diff --git a/Cargo.lock b/Cargo.lock index 60cc5adefc..a8f1389182 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4713,6 +4713,7 @@ name = "solana-client" version = "1.10.0" dependencies = [ "assert_matches", + "async-trait", "base64 0.13.0", "bincode", "bs58 0.4.0", @@ -5985,6 +5986,7 @@ dependencies = [ "solana-runtime", "solana-sdk", "solana-streamer", + "tokio", ] [[package]] diff --git a/cli/src/cli.rs b/cli/src/cli.rs index ce6df92bd3..bdf36e67f0 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1706,7 +1706,7 @@ mod tests { serde_json::{json, Value}, solana_client::{ blockhash_query, - mock_sender::SIGNATURE, + mock_sender_for_cli::SIGNATURE, rpc_request::RpcRequest, rpc_response::{Response, RpcResponseContext}, }, diff --git a/client/Cargo.toml b/client/Cargo.toml index dd4fb7d307..52752b49ea 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -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" diff --git a/client/src/http_sender.rs b/client/src/http_sender.rs index 0a7558c6f1..56950399d2 100644 --- a/client/src/http_sender.rs +++ b/client/src/http_sender.rs @@ -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, + client: Arc, url: String, request_id: AtomicU64, stats: RwLock, } -/// 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 { + async fn send( + &self, + request: RpcRequest, + params: serde_json::Value, + ) -> Result { 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::())?; + let mut json = response.json::().await?; if json["error"].is_object() { return match serde_json::from_value::(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; } } diff --git a/client/src/lib.rs b/client/src/lib.rs index 0794d0f4c5..72cb649686 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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"; +} diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index 2fa8d8642a..aa301bc426 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -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; 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 { + async fn send( + &self, + request: RpcRequest, + params: serde_json::Value, + ) -> Result { 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, diff --git a/client/src/nonblocking/mod.rs b/client/src/nonblocking/mod.rs new file mode 100644 index 0000000000..9fa9f86a74 --- /dev/null +++ b/client/src/nonblocking/mod.rs @@ -0,0 +1 @@ +pub mod rpc_client; diff --git a/client/src/nonblocking/rpc_client.rs b/client/src/nonblocking/rpc_client.rs new file mode 100644 index 0000000000..e32f1d946a --- /dev/null +++ b/client/src/nonblocking/rpc_client.rs @@ -0,0 +1,5065 @@ +//! Communication with a Solana node over RPC asynchronously . +//! +//! 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, + RpcGetConfirmedSignaturesForAddress2Config, +}; +use { + crate::{ + client_error::{ClientError, ClientErrorKind, Result as ClientResult}, + http_sender::HttpSender, + mock_sender::{MockSender, Mocks}, + rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClientConfig}, + rpc_config::{RpcAccountInfoConfig, *}, + rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, + rpc_response::*, + rpc_sender::*, + spinner, + }, + bincode::serialize, + log::*, + serde_json::{json, Value}, + solana_account_decoder::{ + parse_token::{TokenAccountType, UiTokenAccount, UiTokenAmount}, + UiAccount, UiAccountData, UiAccountEncoding, + }, + solana_sdk::{ + account::Account, + clock::{Epoch, Slot, UnixTimestamp, DEFAULT_MS_PER_SLOT, MAX_HASH_AGE_IN_SECONDS}, + commitment_config::{CommitmentConfig, CommitmentLevel}, + epoch_info::EpochInfo, + epoch_schedule::EpochSchedule, + fee_calculator::{FeeCalculator, FeeRateGovernor}, + hash::Hash, + message::Message, + pubkey::Pubkey, + signature::Signature, + transaction::{self, uses_durable_nonce, Transaction}, + }, + solana_transaction_status::{ + EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, + UiConfirmedBlock, UiTransactionEncoding, + }, + solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY, + std::{ + cmp::min, + net::SocketAddr, + str::FromStr, + sync::RwLock, + time::{Duration, Instant}, + }, + tokio::time::sleep, +}; + +/// 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. +/// +/// This type builds on the underlying RPC protocol, adding extra features such +/// as timeout handling, retries, and waiting on transaction [commitment levels][cl]. +/// Some methods simply pass through to the underlying RPC protocol. Not all RPC +/// methods are encapsulated by this type, but `RpcClient` does expose a generic +/// [`send`](RpcClient::send) method for making any [`RpcRequest`]. +/// +/// The documentation for most `RpcClient` methods contains an "RPC Reference" +/// section that links to the documentation for the underlying JSON-RPC method. +/// The documentation for `RpcClient` does not reproduce the documentation for +/// the underlying JSON-RPC methods. Thus reading both is necessary for complete +/// understanding. +/// +/// `RpcClient`s generally communicate over HTTP on port 8899, a typical server +/// URL being "http://localhost:8899". +/// +/// Methods that query information from recent [slots], including those that +/// confirm transactions, decide the most recent slot to query based on a +/// [commitment level][cl], which determines how committed or finalized a slot +/// must be to be considered for the query. Unless specified otherwise, the +/// commitment level is [`Finalized`], meaning the slot is definitely +/// permanently committed. The default commitment level can be configured by +/// creating `RpcClient` with an explicit [`CommitmentConfig`], and that default +/// configured commitment level can be overridden by calling the various +/// `_with_commitment` methods, like +/// [`RpcClient::confirm_transaction_with_commitment`]. In some cases the +/// configured commitment level is ignored and `Finalized` is used instead, as +/// in [`RpcClient::get_blocks`], where it would be invalid to use the +/// [`Processed`] commitment level. These exceptions are noted in the method +/// documentation. +/// +/// [`Finalized`]: CommitmentLevel::Finalized +/// [`Processed`]: CommitmentLevel::Processed +/// [jsonprot]: https://docs.solana.com/developing/clients/jsonrpc-api +/// [JSON-RPC]: https://www.jsonrpc.org/specification +/// [slots]: https://docs.solana.com/terminology#slot +/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment +/// +/// # Errors +/// +/// Methods on `RpcClient` return +/// [`client_error::Result`][crate::client_error::Result], and many of them +/// return the [`RpcResult`][crate::rpc_response::RpcResult] typedef, which +/// contains [`Response`][crate::rpc_response::Response] on `Ok`. Both +/// `client_error::Result` and [`RpcResult`] contain `ClientError` on error. In +/// the case of `RpcResult`, the actual return value is in the +/// [`value`][crate::rpc_response::Response::value] field, with RPC contextual +/// information in the [`context`][crate::rpc_response::Response::context] +/// field, so it is common for the value to be accessed with `?.value`, as in +/// +/// ``` +/// # use solana_sdk::system_transaction; +/// # use solana_client::rpc_client::RpcClient; +/// # use solana_client::client_error::ClientError; +/// # use solana_sdk::signature::{Keypair, Signer}; +/// # use solana_sdk::hash::Hash; +/// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); +/// # let key = Keypair::new(); +/// # let to = solana_sdk::pubkey::new_rand(); +/// # let lamports = 50; +/// # let latest_blockhash = Hash::default(); +/// # let tx = system_transaction::transfer(&key, &to, lamports, latest_blockhash); +/// let signature = rpc_client.send_transaction(&tx)?; +/// let statuses = rpc_client.get_signature_statuses(&[signature])?.value; +/// # Ok::<(), ClientError>(()) +/// ``` +/// +/// 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. +pub struct RpcClient { + sender: Box, + config: RpcClientConfig, + node_version: RwLock>, +} + +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. + pub(crate) fn new_sender( + sender: T, + config: RpcClientConfig, + ) -> Self { + Self { + sender: Box::new(sender), + node_version: RwLock::new(None), + config, + } + } + + /// 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][cl] of [`Finalized`](CommitmentLevel::Finalized). + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # 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][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// 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), + RpcClientConfig::with_commitment(commitment_config), + ) + } + + /// 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][cl] of + /// [`Finalized`](CommitmentLevel::Finalized). + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # 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), + RpcClientConfig::with_commitment(CommitmentConfig::default()), + ) + } + + /// Create an HTTP `RpcClient` with specified timeout and [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// 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, + commitment_config: CommitmentConfig, + ) -> Self { + Self::new_sender( + HttpSender::new_with_timeout(url, timeout), + RpcClientConfig::with_commitment(commitment_config), + ) + } + + /// Create an HTTP `RpcClient` with specified timeout and [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// 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, + commitment_config: CommitmentConfig, + confirm_transaction_initial_timeout: Duration, + ) -> Self { + Self::new_sender( + HttpSender::new_with_timeout(url, timeout), + RpcClientConfig { + commitment_config, + confirm_transaction_initial_timeout: Some(confirm_transaction_initial_timeout), + }, + ) + } + + /// 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), + RpcClientConfig::with_commitment(CommitmentConfig::default()), + ) + } + + /// 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, + /// # rpc_response::{Response, RpcResponseContext}, + /// # }; + /// # use std::collections::HashMap; + /// # use serde_json::json; + /// // 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), + RpcClientConfig::with_commitment(CommitmentConfig::default()), + ) + } + + /// Create an HTTP `RpcClient` from a [`SocketAddr`]. + /// + /// The client has a default timeout of 30 seconds, and a default [commitment + /// level][cl] of [`Finalized`](CommitmentLevel::Finalized). + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # 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][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// 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, + ) -> Self { + 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 a default [commitment level][cl] of [`Finalized`](CommitmentLevel::Finalized). + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # 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) + } + + async fn get_node_version(&self) -> Result { + let r_node_version = self.node_version.read().unwrap(); + if let Some(version) = &*r_node_version { + Ok(version.clone()) + } else { + drop(r_node_version); + let mut w_node_version = self.node_version.write().unwrap(); + let node_version = self.get_version().await.map_err(|e| { + RpcError::RpcRequestError(format!("cluster version query failed: {}", e)) + })?; + let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| { + RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e)) + })?; + *w_node_version = Some(node_version.clone()); + Ok(node_version) + } + } + + /// Get the configured default [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// The commitment config may be specified during construction, and + /// determines how thoroughly committed a transaction must be when waiting + /// for its confirmation or otherwise checking for confirmation. If not + /// specified, the default commitment level is + /// [`Finalized`](CommitmentLevel::Finalized). + /// + /// The default commitment level is overridden when calling methods that + /// explicitly provide a [`CommitmentConfig`], like + /// [`RpcClient::confirm_transaction_with_commitment`]. + pub fn commitment(&self) -> CommitmentConfig { + self.config.commitment_config + } + + async fn use_deprecated_commitment(&self) -> Result { + Ok(self.get_node_version().await? < semver::Version::new(1, 5, 5)) + } + + async fn maybe_map_commitment( + &self, + requested_commitment: CommitmentConfig, + ) -> Result { + if matches!( + requested_commitment.commitment, + CommitmentLevel::Finalized | CommitmentLevel::Confirmed | CommitmentLevel::Processed + ) && self.use_deprecated_commitment().await? + { + return Ok(CommitmentConfig::use_deprecated_commitment( + requested_commitment, + )); + } + Ok(requested_commitment) + } + + #[allow(deprecated)] + async fn maybe_map_request(&self, mut request: RpcRequest) -> Result { + if self.get_node_version().await? < semver::Version::new(1, 7, 0) { + request = match request { + RpcRequest::GetBlock => RpcRequest::GetConfirmedBlock, + RpcRequest::GetBlocks => RpcRequest::GetConfirmedBlocks, + RpcRequest::GetBlocksWithLimit => RpcRequest::GetConfirmedBlocksWithLimit, + RpcRequest::GetSignaturesForAddress => { + RpcRequest::GetConfirmedSignaturesForAddress2 + } + RpcRequest::GetTransaction => RpcRequest::GetConfirmedTransaction, + _ => request, + }; + } + Ok(request) + } + + /// Submit a transaction and wait for confirmation. + /// + /// Once this function returns successfully, the given transaction is + /// guaranteed to be processed with the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// After sending the transaction, this method polls in a loop for the + /// status of the transaction until it has ben confirmed. + /// + /// # Errors + /// + /// If the transaction is not signed then an error with kind [`RpcError`] is + /// returned, containing an [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]. + /// + /// If the preflight transaction simulation fails then an error with kind + /// [`RpcError`] is returned, containing an [`RpcResponseError`] with `code` + /// set to [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]. + /// + /// If the receiving node is unhealthy, e.g. it is not fully synced to + /// the cluster, then an error with kind [`RpcError`] is returned, + /// containing an [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]. + /// + /// [`RpcResponseError`]: RpcError::RpcResponseError + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE + /// [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY + /// + /// # RPC Reference + /// + /// This method is built on the [`sendTransaction`] RPC method, and the + /// [`getLatestBlockhash`] RPC method. + /// + /// [`sendTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + /// [`getLatestBlockhash`]: https://docs.solana.com/developing/clients/jsonrpc-api#getlatestblockhash + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_and_confirm_transaction(&tx)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn send_and_confirm_transaction( + &self, + transaction: &Transaction, + ) -> ClientResult { + const SEND_RETRIES: usize = 1; + const GET_STATUS_RETRIES: usize = usize::MAX; + + 'sending: for _ in 0..SEND_RETRIES { + let signature = self.send_transaction(transaction).await?; + + let recent_blockhash = if uses_durable_nonce(transaction).is_some() { + let (recent_blockhash, ..) = self + .get_latest_blockhash_with_commitment(CommitmentConfig::processed()) + .await?; + recent_blockhash + } else { + transaction.message.recent_blockhash + }; + + for status_retry in 0..GET_STATUS_RETRIES { + match self.get_signature_status(&signature).await? { + Some(Ok(_)) => return Ok(signature), + Some(Err(e)) => return Err(e.into()), + None => { + if !self + .is_blockhash_valid(&recent_blockhash, CommitmentConfig::processed()) + .await? + { + // Block hash is not found by some reason + break 'sending; + } else if cfg!(not(test)) + // Ignore sleep at last step. + && status_retry < GET_STATUS_RETRIES + { + // Retry twice a second + sleep(Duration::from_millis(500)).await; + continue; + } + } + } + } + } + + Err(RpcError::ForUser( + "unable to confirm transaction. \ + This can happen in situations such as transaction expiration \ + and insufficient fee-payer funds" + .to_string(), + ) + .into()) + } + + pub async fn send_and_confirm_transaction_with_spinner( + &self, + transaction: &Transaction, + ) -> ClientResult { + self.send_and_confirm_transaction_with_spinner_and_commitment( + transaction, + self.commitment(), + ) + .await + } + + pub async fn send_and_confirm_transaction_with_spinner_and_commitment( + &self, + transaction: &Transaction, + commitment: CommitmentConfig, + ) -> ClientResult { + self.send_and_confirm_transaction_with_spinner_and_config( + transaction, + commitment, + RpcSendTransactionConfig { + preflight_commitment: Some(commitment.commitment), + ..RpcSendTransactionConfig::default() + }, + ) + .await + } + + pub async fn send_and_confirm_transaction_with_spinner_and_config( + &self, + transaction: &Transaction, + commitment: CommitmentConfig, + config: RpcSendTransactionConfig, + ) -> ClientResult { + let recent_blockhash = if uses_durable_nonce(transaction).is_some() { + self.get_latest_blockhash_with_commitment(CommitmentConfig::processed()) + .await? + .0 + } else { + transaction.message.recent_blockhash + }; + let signature = self + .send_transaction_with_config(transaction, config) + .await?; + self.confirm_transaction_with_spinner(&signature, &recent_blockhash, commitment) + .await?; + Ok(signature) + } + + /// Submits a signed transaction to the network. + /// + /// Before a transaction is processed, the receiving node runs a "preflight + /// check" which verifies signatures, checks that the node is healthy, + /// and simulates the transaction. If the preflight check fails then an + /// error is returned immediately. Preflight checks can be disabled by + /// calling [`send_transaction_with_config`] and setting the + /// [`skip_preflight`] field of [`RpcSendTransactionConfig`] to `true`. + /// + /// This method does not wait for the transaction to be processed or + /// confirmed before returning successfully. To wait for the transaction to + /// be processed or confirmed, use the [`send_and_confirm_transaction`] + /// method. + /// + /// [`send_transaction_with_config`]: RpcClient::send_transaction_with_config + /// [`skip_preflight`]: crate::rpc_config::RpcSendTransactionConfig::skip_preflight + /// [`RpcSendTransactionConfig`]: crate::rpc_config::RpcSendTransactionConfig + /// [`send_and_confirm_transaction`]: RpcClient::send_and_confirm_transaction + /// + /// # Errors + /// + /// If the transaction is not signed then an error with kind [`RpcError`] is + /// returned, containing an [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]. + /// + /// If the preflight transaction simulation fails then an error with kind + /// [`RpcError`] is returned, containing an [`RpcResponseError`] with `code` + /// set to [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]. + /// + /// If the receiving node is unhealthy, e.g. it is not fully synced to + /// the cluster, then an error with kind [`RpcError`] is returned, + /// containing an [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]. + /// + /// [`RpcResponseError`]: RpcError::RpcResponseError + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE + /// [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY + /// + /// # RPC Reference + /// + /// This method is built on the [`sendTransaction`] RPC method. + /// + /// [`sendTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn send_transaction(&self, transaction: &Transaction) -> ClientResult { + self.send_transaction_with_config( + transaction, + RpcSendTransactionConfig { + preflight_commitment: Some( + self.maybe_map_commitment(self.commitment()) + .await? + .commitment, + ), + ..RpcSendTransactionConfig::default() + }, + ) + .await + } + + /// Submits a signed transaction to the network. + /// + /// Before a transaction is processed, the receiving node runs a "preflight + /// check" which verifies signatures, checks that the node is healthy, and + /// simulates the transaction. If the preflight check fails then an error is + /// returned immediately. Preflight checks can be disabled by setting the + /// [`skip_preflight`] field of [`RpcSendTransactionConfig`] to `true`. + /// + /// This method does not wait for the transaction to be processed or + /// confirmed before returning successfully. To wait for the transaction to + /// be processed or confirmed, use the [`send_and_confirm_transaction`] + /// method. + /// + /// [`send_transaction_with_config`]: RpcClient::send_transaction_with_config + /// [`skip_preflight`]: crate::rpc_config::RpcSendTransactionConfig::skip_preflight + /// [`RpcSendTransactionConfig`]: crate::rpc_config::RpcSendTransactionConfig + /// [`send_and_confirm_transaction`]: RpcClient::send_and_confirm_transaction + /// + /// # Errors + /// + /// If preflight checks are enabled, if the transaction is not signed + /// then an error with kind [`RpcError`] is returned, containing an + /// [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]. + /// + /// If preflight checks are enabled, if the preflight transaction simulation + /// fails then an error with kind [`RpcError`] is returned, containing an + /// [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]. + /// + /// If the receiving node is unhealthy, e.g. it is not fully synced to + /// the cluster, then an error with kind [`RpcError`] is returned, + /// containing an [`RpcResponseError`] with `code` set to + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]. + /// + /// [`RpcResponseError`]: RpcError::RpcResponseError + /// [`JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE + /// [`JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE + /// [`JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY`]: crate::rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY + /// + /// # RPC Reference + /// + /// This method is built on the [`sendTransaction`] RPC method. + /// + /// [`sendTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # rpc_config::RpcSendTransactionConfig, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let config = RpcSendTransactionConfig { + /// skip_preflight: true, + /// .. RpcSendTransactionConfig::default() + /// }; + /// let signature = rpc_client.send_transaction_with_config( + /// &tx, + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn send_transaction_with_config( + &self, + transaction: &Transaction, + config: RpcSendTransactionConfig, + ) -> ClientResult { + let encoding = if let Some(encoding) = config.encoding { + encoding + } else { + self.default_cluster_transaction_encoding().await? + }; + let preflight_commitment = CommitmentConfig { + commitment: config.preflight_commitment.unwrap_or_default(), + }; + let preflight_commitment = self.maybe_map_commitment(preflight_commitment).await?; + let config = RpcSendTransactionConfig { + encoding: Some(encoding), + preflight_commitment: Some(preflight_commitment.commitment), + ..config + }; + let serialized_encoded = serialize_and_encode::(transaction, encoding)?; + let signature_base58_str: String = match self + .send( + RpcRequest::SendTransaction, + json!([serialized_encoded, config]), + ) + .await + { + Ok(signature_base58_str) => signature_base58_str, + Err(err) => { + if let ClientErrorKind::RpcError(RpcError::RpcResponseError { + code, + message, + data, + }) = &err.kind + { + debug!("{} {}", code, message); + if let RpcResponseErrorData::SendTransactionPreflightFailure( + RpcSimulateTransactionResult { + logs: Some(logs), .. + }, + ) = data + { + for (i, log) in logs.iter().enumerate() { + debug!("{:>3}: {}", i + 1, log); + } + debug!(""); + } + } + return Err(err); + } + }; + + let signature = signature_base58_str + .parse::() + .map_err(|err| Into::::into(RpcError::ParseError(err.to_string())))?; + // A mismatching RPC response signature indicates an issue with the RPC node, and + // should not be passed along to confirmation methods. The transaction may or may + // not have been submitted to the cluster, so callers should verify the success of + // the correct transaction signature independently. + if signature != transaction.signatures[0] { + Err(RpcError::RpcRequestError(format!( + "RPC node returned mismatched signature {:?}, expected {:?}", + signature, transaction.signatures[0] + )) + .into()) + } else { + Ok(transaction.signatures[0]) + } + } + + /// Check the confirmation status of a transaction. + /// + /// Returns `true` if the given transaction succeeded and has been committed + /// with the configured [commitment level][cl], which can be retrieved with + /// the [`commitment`](RpcClient::commitment) method. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// Note that this method does not wait for a transaction to be confirmed + /// — it only checks whether a transaction has been confirmed. To + /// submit a transaction and wait for it to confirm, use + /// [`send_and_confirm_transaction`][RpcClient::send_and_confirm_transaction]. + /// + /// _This method returns `false` if the transaction failed, even if it has + /// been confirmed._ + /// + /// # RPC Reference + /// + /// This method is built on the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob and wait for confirmation + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// + /// loop { + /// let confirmed = rpc_client.confirm_transaction(&signature)?; + /// if confirmed { + /// break; + /// } + /// } + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn confirm_transaction(&self, signature: &Signature) -> ClientResult { + Ok(self + .confirm_transaction_with_commitment(signature, self.commitment()) + .await? + .value) + } + + /// Check the confirmation status of a transaction. + /// + /// Returns an [`RpcResult`] with value `true` if the given transaction + /// succeeded and has been committed with the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// Note that this method does not wait for a transaction to be confirmed + /// — it only checks whether a transaction has been confirmed. To + /// submit a transaction and wait for it to confirm, use + /// [`send_and_confirm_transaction`][RpcClient::send_and_confirm_transaction]. + /// + /// _This method returns an [`RpcResult`] with value `false` if the + /// transaction failed, even if it has been confirmed._ + /// + /// # RPC Reference + /// + /// This method is built on the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::{ + /// # commitment_config::CommitmentConfig, + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # use std::time::Duration; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob and wait for confirmation + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// + /// loop { + /// let commitment_config = CommitmentConfig::processed(); + /// let confirmed = rpc_client.confirm_transaction_with_commitment(&signature, commitment_config)?; + /// if confirmed.value { + /// break; + /// } + /// } + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn confirm_transaction_with_commitment( + &self, + signature: &Signature, + commitment_config: CommitmentConfig, + ) -> RpcResult { + let Response { context, value } = self.get_signature_statuses(&[*signature]).await?; + + Ok(Response { + context, + value: value[0] + .as_ref() + .filter(|result| result.satisfies_commitment(commitment_config)) + .map(|result| result.status.is_ok()) + .unwrap_or_default(), + }) + } + + pub async fn confirm_transaction_with_spinner( + &self, + signature: &Signature, + recent_blockhash: &Hash, + commitment: CommitmentConfig, + ) -> ClientResult<()> { + let desired_confirmations = if commitment.is_finalized() { + MAX_LOCKOUT_HISTORY + 1 + } else { + 1 + }; + let mut confirmations = 0; + + let progress_bar = spinner::new_progress_bar(); + + progress_bar.set_message(format!( + "[{}/{}] Finalizing transaction {}", + confirmations, desired_confirmations, signature, + )); + + let now = Instant::now(); + let confirm_transaction_initial_timeout = self + .config + .confirm_transaction_initial_timeout + .unwrap_or_default(); + let (signature, status) = loop { + // Get recent commitment in order to count confirmations for successful transactions + let status = self + .get_signature_status_with_commitment(signature, CommitmentConfig::processed()) + .await?; + if status.is_none() { + let blockhash_not_found = !self + .is_blockhash_valid(recent_blockhash, CommitmentConfig::processed()) + .await?; + if blockhash_not_found && now.elapsed() >= confirm_transaction_initial_timeout { + break (signature, status); + } + } else { + break (signature, status); + } + + if cfg!(not(test)) { + sleep(Duration::from_millis(500)).await; + } + }; + if let Some(result) = status { + if let Err(err) = result { + return Err(err.into()); + } + } else { + return Err(RpcError::ForUser( + "unable to confirm transaction. \ + This can happen in situations such as transaction expiration \ + and insufficient fee-payer funds" + .to_string(), + ) + .into()); + } + let now = Instant::now(); + loop { + // Return when specified commitment is reached + // Failed transactions have already been eliminated, `is_some` check is sufficient + if self + .get_signature_status_with_commitment(signature, commitment) + .await? + .is_some() + { + progress_bar.set_message("Transaction confirmed"); + progress_bar.finish_and_clear(); + return Ok(()); + } + + progress_bar.set_message(format!( + "[{}/{}] Finalizing transaction {}", + min(confirmations + 1, desired_confirmations), + desired_confirmations, + signature, + )); + sleep(Duration::from_millis(500)).await; + confirmations = self + .get_num_blocks_since_signature_confirmation(signature) + .await + .unwrap_or(confirmations); + if now.elapsed().as_secs() >= MAX_HASH_AGE_IN_SECONDS as u64 { + return Err( + RpcError::ForUser("transaction not finalized. \ + This can happen when a transaction lands in an abandoned fork. \ + Please retry.".to_string()).into(), + ); + } + } + } + + async fn default_cluster_transaction_encoding( + &self, + ) -> Result { + if self.get_node_version().await? < semver::Version::new(1, 3, 16) { + Ok(UiTransactionEncoding::Base58) + } else { + Ok(UiTransactionEncoding::Base64) + } + } + + /// Simulates sending a transaction. + /// + /// If the transaction fails, then the [`err`] field of the returned + /// [`RpcSimulateTransactionResult`] will be `Some`. Any logs emitted from + /// the transaction are returned in the [`logs`] field. + /// + /// [`err`]: crate::rpc_response::RpcSimulateTransactionResult::err + /// [`logs`]: crate::rpc_response::RpcSimulateTransactionResult::logs + /// + /// Simulating a transaction is similar to the ["preflight check"] that is + /// run by default when sending a transaction. + /// + /// ["preflight check"]: https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + /// + /// By default, signatures are not verified during simulation. To verify + /// signatures, call the [`simulate_transaction_with_config`] method, with + /// the [`sig_verify`] field of [`RpcSimulateTransactionConfig`] set to + /// `true`. + /// + /// [`simulate_transaction_with_config`]: RpcClient::simulate_transaction_with_config + /// [`sig_verify`]: crate::rpc_config::RpcSimulateTransactionConfig::sig_verify + /// + /// # RPC Reference + /// + /// This method is built on the [`simulateTransaction`] RPC method. + /// + /// [`simulateTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#simulatetransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # rpc_response::RpcSimulateTransactionResult, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let result = rpc_client.simulate_transaction(&tx)?; + /// assert!(result.value.err.is_none()); + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn simulate_transaction( + &self, + transaction: &Transaction, + ) -> RpcResult { + self.simulate_transaction_with_config( + transaction, + RpcSimulateTransactionConfig { + commitment: Some(self.commitment()), + ..RpcSimulateTransactionConfig::default() + }, + ) + .await + } + + /// Simulates sending a transaction. + /// + /// If the transaction fails, then the [`err`] field of the returned + /// [`RpcSimulateTransactionResult`] will be `Some`. Any logs emitted from + /// the transaction are returned in the [`logs`] field. + /// + /// [`err`]: crate::rpc_response::RpcSimulateTransactionResult::err + /// [`logs`]: crate::rpc_response::RpcSimulateTransactionResult::logs + /// + /// Simulating a transaction is similar to the ["preflight check"] that is + /// run by default when sending a transaction. + /// + /// ["preflight check"]: https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + /// + /// By default, signatures are not verified during simulation. To verify + /// signatures, call the [`simulate_transaction_with_config`] method, with + /// the [`sig_verify`] field of [`RpcSimulateTransactionConfig`] set to + /// `true`. + /// + /// [`simulate_transaction_with_config`]: RpcClient::simulate_transaction_with_config + /// [`sig_verify`]: crate::rpc_config::RpcSimulateTransactionConfig::sig_verify + /// + /// This method can additionally query information about accounts by + /// including them in the [`accounts`] field of the + /// [`RpcSimulateTransactionConfig`] argument, in which case those results + /// are reported in the [`accounts`][accounts2] field of the returned + /// [`RpcSimulateTransactionResult`]. + /// + /// [`accounts`]: crate::rpc_config::RpcSimulateTransactionConfig::accounts + /// [accounts2]: crate::rpc_response::RpcSimulateTransactionResult::accounts + /// + /// # RPC Reference + /// + /// This method is built on the [`simulateTransaction`] RPC method. + /// + /// [`simulateTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#simulatetransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # rpc_config::RpcSimulateTransactionConfig, + /// # rpc_response::RpcSimulateTransactionResult, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Transfer lamports from Alice to Bob + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let config = RpcSimulateTransactionConfig { + /// sig_verify: true, + /// .. RpcSimulateTransactionConfig::default() + /// }; + /// let result = rpc_client.simulate_transaction_with_config( + /// &tx, + /// config, + /// )?; + /// assert!(result.value.err.is_none()); + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn simulate_transaction_with_config( + &self, + transaction: &Transaction, + config: RpcSimulateTransactionConfig, + ) -> RpcResult { + let encoding = if let Some(encoding) = config.encoding { + encoding + } else { + self.default_cluster_transaction_encoding().await? + }; + let commitment = config.commitment.unwrap_or_default(); + let commitment = self.maybe_map_commitment(commitment).await?; + let config = RpcSimulateTransactionConfig { + encoding: Some(encoding), + commitment: Some(commitment), + ..config + }; + let serialized_encoded = serialize_and_encode::(transaction, encoding)?; + self.send( + RpcRequest::SimulateTransaction, + json!([serialized_encoded, config]), + ) + .await + } + + /// Returns the highest slot information that the node has snapshots for. + /// + /// This will find the highest full snapshot slot, and the highest incremental snapshot slot + /// _based on_ the full snapshot slot, if there is one. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getHighestSnapshotSlot`] RPC method. + /// + /// [`getHighestSnapshotSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#gethighestsnapshotslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let snapshot_slot_info = rpc_client.get_highest_snapshot_slot()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_highest_snapshot_slot(&self) -> ClientResult { + if self.get_node_version().await? < semver::Version::new(1, 9, 0) { + #[allow(deprecated)] + self.get_snapshot_slot() + .await + .map(|full| RpcSnapshotSlotInfo { + full, + incremental: None, + }) + } else { + self.send(RpcRequest::GetHighestSnapshotSlot, Value::Null) + .await + } + } + + #[deprecated( + since = "1.8.0", + note = "Please use RpcClient::get_highest_snapshot_slot() instead" + )] + #[allow(deprecated)] + pub async fn get_snapshot_slot(&self) -> ClientResult { + self.send(RpcRequest::GetSnapshotSlot, Value::Null).await + } + + /// Check if a transaction has been processed with the default [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// If the transaction has been processed with the default commitment level, + /// then this method returns `Ok` of `Some`. If the transaction has not yet + /// been processed with the default commitment level, it returns `Ok` of + /// `None`. + /// + /// If the transaction has been processed with the default commitment level, + /// and the transaction succeeded, this method returns `Ok(Some(Ok(())))`. + /// If the transaction has peen processed with the default commitment level, + /// and the transaction failed, this method returns `Ok(Some(Err(_)))`, + /// where the interior error is type [`TransactionError`]. + /// + /// [`TransactionError`]: solana_sdk::transaction::TransactionError + /// + /// This function only searches a node's recent history, including all + /// recent slots, plus up to + /// [`MAX_RECENT_BLOCKHASHES`][solana_sdk::clock::MAX_RECENT_BLOCKHASHES] + /// rooted slots. To search the full transaction history use the + /// [`get_signature_statuse_with_commitment_and_history`][RpcClient::get_signature_status_with_commitment_and_history] + /// method. + /// + /// # RPC Reference + /// + /// This method is built on the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#gitsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// let status = rpc_client.get_signature_status(&signature)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signature_status( + &self, + signature: &Signature, + ) -> ClientResult>> { + self.get_signature_status_with_commitment(signature, self.commitment()) + .await + } + + /// Gets the statuses of a list of transaction signatures. + /// + /// The returned vector of [`TransactionStatus`] has the same length as the + /// input slice. + /// + /// For any transaction that has not been processed by the network, the + /// value of the corresponding entry in the returned vector is `None`. As a + /// result, a transaction that has recently been submitted will not have a + /// status immediately. + /// + /// To submit a transaction and wait for it to confirm, use + /// [`send_and_confirm_transaction`][RpcClient::send_and_confirm_transaction]. + /// + /// This function ignores the configured confirmation level, and returns the + /// transaction status whatever it is. It does not wait for transactions to + /// be processed. + /// + /// This function only searches a node's recent history, including all + /// recent slots, plus up to + /// [`MAX_RECENT_BLOCKHASHES`][solana_sdk::clock::MAX_RECENT_BLOCKHASHES] + /// rooted slots. To search the full transaction history use the + /// [`get_signature_statuses_with_history`][RpcClient::get_signature_statuses_with_history] + /// method. + /// + /// # Errors + /// + /// Any individual `TransactionStatus` may have triggered an error during + /// processing, in which case its [`err`][`TransactionStatus::err`] field + /// will be `Some`. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # use std::time::Duration; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// // Send lamports from Alice to Bob and wait for the transaction to be processed + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// + /// let status = loop { + /// let statuses = rpc_client.get_signature_statuses(&[signature])?.value; + /// if let Some(status) = statuses[0].clone() { + /// break status; + /// } + /// std::thread::sleep(Duration::from_millis(100)); + /// }; + /// + /// assert!(status.err.is_none()); + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signature_statuses( + &self, + signatures: &[Signature], + ) -> RpcResult>> { + let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect(); + self.send(RpcRequest::GetSignatureStatuses, json!([signatures])) + .await + } + + /// Gets the statuses of a list of transaction signatures. + /// + /// The returned vector of [`TransactionStatus`] has the same length as the + /// input slice. + /// + /// For any transaction that has not been processed by the network, the + /// value of the corresponding entry in the returned vector is `None`. As a + /// result, a transaction that has recently been submitted will not have a + /// status immediately. + /// + /// To submit a transaction and wait for it to confirm, use + /// [`send_and_confirm_transaction`][RpcClient::send_and_confirm_transaction]. + /// + /// This function ignores the configured confirmation level, and returns the + /// transaction status whatever it is. It does not wait for transactions to + /// be processed. + /// + /// This function searches a node's full ledger history and (if implemented) long-term storage. To search for + /// transactions in recent slots only use the + /// [`get_signature_statuses`][RpcClient::get_signature_statuses] method. + /// + /// # Errors + /// + /// Any individual `TransactionStatus` may have triggered an error during + /// processing, in which case its [`err`][`TransactionStatus::err`] field + /// will be `Some`. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSignatureStatuses`] RPC + /// method, with the `searchTransactionHistory` configuration option set to + /// `true`. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # hash::Hash, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # fn get_old_transaction_signature() -> Signature { Signature::default() } + /// // Check if an old transaction exists + /// let signature = get_old_transaction_signature(); + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let statuses = rpc_client.get_signature_statuses_with_history(&[signature])?.value; + /// if statuses[0].is_none() { + /// println!("old transaction does not exist"); + /// } + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signature_statuses_with_history( + &self, + signatures: &[Signature], + ) -> RpcResult>> { + let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect(); + self.send( + RpcRequest::GetSignatureStatuses, + json!([signatures, { + "searchTransactionHistory": true + }]), + ) + .await + } + + /// Check if a transaction has been processed with the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// If the transaction has been processed with the given commitment level, + /// then this method returns `Ok` of `Some`. If the transaction has not yet + /// been processed with the given commitment level, it returns `Ok` of + /// `None`. + /// + /// If the transaction has been processed with the given commitment level, + /// and the transaction succeeded, this method returns `Ok(Some(Ok(())))`. + /// If the transaction has peen processed with the given commitment level, + /// and the transaction failed, this method returns `Ok(Some(Err(_)))`, + /// where the interior error is type [`TransactionError`]. + /// + /// [`TransactionError`]: solana_sdk::transaction::TransactionError + /// + /// This function only searches a node's recent history, including all + /// recent slots, plus up to + /// [`MAX_RECENT_BLOCKHASHES`][solana_sdk::clock::MAX_RECENT_BLOCKHASHES] + /// rooted slots. To search the full transaction history use the + /// [`get_signature_statuse_with_commitment_and_history`][RpcClient::get_signature_status_with_commitment_and_history] + /// method. + /// + /// # RPC Reference + /// + /// This method is built on the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # commitment_config::CommitmentConfig, + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_and_confirm_transaction(&tx)?; + /// let commitment_config = CommitmentConfig::processed(); + /// let status = rpc_client.get_signature_status_with_commitment( + /// &signature, + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signature_status_with_commitment( + &self, + signature: &Signature, + commitment_config: CommitmentConfig, + ) -> ClientResult>> { + let result: Response>> = self + .send( + RpcRequest::GetSignatureStatuses, + json!([[signature.to_string()]]), + ) + .await?; + Ok(result.value[0] + .clone() + .filter(|result| result.satisfies_commitment(commitment_config)) + .map(|status_meta| status_meta.status)) + } + + /// Check if a transaction has been processed with the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// If the transaction has been processed with the given commitment level, + /// then this method returns `Ok` of `Some`. If the transaction has not yet + /// been processed with the given commitment level, it returns `Ok` of + /// `None`. + /// + /// If the transaction has been processed with the given commitment level, + /// and the transaction succeeded, this method returns `Ok(Some(Ok(())))`. + /// If the transaction has peen processed with the given commitment level, + /// and the transaction failed, this method returns `Ok(Some(Err(_)))`, + /// where the interior error is type [`TransactionError`]. + /// + /// [`TransactionError`]: solana_sdk::transaction::TransactionError + /// + /// This method optionally searches a node's full ledger history and (if + /// implemented) long-term storage. + /// + /// # RPC Reference + /// + /// This method is built on the [`getSignatureStatuses`] RPC method. + /// + /// [`getSignatureStatuses`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturestatuses + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # commitment_config::CommitmentConfig, + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_transaction(&tx)?; + /// let commitment_config = CommitmentConfig::processed(); + /// let search_transaction_history = true; + /// let status = rpc_client.get_signature_status_with_commitment_and_history( + /// &signature, + /// commitment_config, + /// search_transaction_history, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signature_status_with_commitment_and_history( + &self, + signature: &Signature, + commitment_config: CommitmentConfig, + search_transaction_history: bool, + ) -> ClientResult>> { + let result: Response>> = self + .send( + RpcRequest::GetSignatureStatuses, + json!([[signature.to_string()], { + "searchTransactionHistory": search_transaction_history + }]), + ) + .await?; + Ok(result.value[0] + .clone() + .filter(|result| result.satisfies_commitment(commitment_config)) + .map(|status_meta| status_meta.status)) + } + + /// Returns the slot that has reached the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSlot`] RPC method. + /// + /// [`getSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#getslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let slot = rpc_client.get_slot()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_slot(&self) -> ClientResult { + self.get_slot_with_commitment(self.commitment()).await + } + + /// Returns the slot that has reached the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSlot`] RPC method. + /// + /// [`getSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#getslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::processed(); + /// let slot = rpc_client.get_slot_with_commitment(commitment_config)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_slot_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.send( + RpcRequest::GetSlot, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + } + + /// Returns the block height that has reached the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method is corresponds directly to the [`getBlockHeight`] RPC method. + /// + /// [`getBlockHeight`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockheight + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let block_height = rpc_client.get_block_height()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_height(&self) -> ClientResult { + self.get_block_height_with_commitment(self.commitment()) + .await + } + + /// Returns the block height that has reached the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method is corresponds directly to the [`getBlockHeight`] RPC method. + /// + /// [`getBlockHeight`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockheight + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::processed(); + /// let block_height = rpc_client.get_block_height_with_commitment( + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_height_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.send( + RpcRequest::GetBlockHeight, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + } + + /// Returns the slot leaders for a given slot range. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSlotLeaders`] RPC method. + /// + /// [`getSlotLeaders`]: https://docs.solana.com/developing/clients/jsonrpc-api#getslotleaders + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::slot_history::Slot; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let start_slot = 1; + /// let limit = 3; + /// let leaders = rpc_client.get_slot_leaders(start_slot, limit)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_slot_leaders( + &self, + start_slot: Slot, + limit: u64, + ) -> ClientResult> { + self.send(RpcRequest::GetSlotLeaders, json!([start_slot, limit])) + .await + .and_then(|slot_leaders: Vec| { + slot_leaders + .iter() + .map(|slot_leader| { + Pubkey::from_str(slot_leader).map_err(|err| { + ClientErrorKind::Custom(format!( + "pubkey deserialization failed: {}", + err + )) + .into() + }) + }) + .collect() + }) + } + + /// Get block production for the current epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlockProduction`] RPC method. + /// + /// [`getBlockProduction`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockproduction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let production = rpc_client.get_block_production()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_production(&self) -> RpcResult { + self.send(RpcRequest::GetBlockProduction, Value::Null).await + } + + /// Get block production for the current or previous epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlockProduction`] RPC method. + /// + /// [`getBlockProduction`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockproduction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # rpc_config::RpcBlockProductionConfig, + /// # rpc_config::RpcBlockProductionConfigRange, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let start_slot = 1; + /// # let limit = 3; + /// let leader = rpc_client.get_slot_leaders(start_slot, limit)?; + /// let leader = leader[0]; + /// let range = RpcBlockProductionConfigRange { + /// first_slot: start_slot, + /// last_slot: Some(start_slot + limit), + /// }; + /// let config = RpcBlockProductionConfig { + /// identity: Some(leader.to_string()), + /// range: Some(range), + /// commitment: Some(CommitmentConfig::processed()), + /// }; + /// let production = rpc_client.get_block_production_with_config( + /// config + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_production_with_config( + &self, + config: RpcBlockProductionConfig, + ) -> RpcResult { + self.send(RpcRequest::GetBlockProduction, json!([config])) + .await + } + + /// Returns epoch activation information for a stake account. + /// + /// This method uses the configured [commitment level]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getStakeActivation`] RPC method. + /// + /// [`getStakeActivation`]: https://docs.solana.com/developing/clients/jsonrpc-api#getstakeactivation + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # rpc_response::StakeActivationState, + /// # }; + /// # use solana_sdk::{ + /// # signer::keypair::Keypair, + /// # signature::Signer, + /// # pubkey::Pubkey, + /// # stake, + /// # stake::state::{Authorized, Lockup}, + /// # transaction::Transaction + /// # }; + /// # use std::str::FromStr; + /// # let alice = Keypair::new(); + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Find some vote account to delegate to + /// let vote_accounts = rpc_client.get_vote_accounts()?; + /// let vote_account = vote_accounts.current.get(0).unwrap_or_else(|| &vote_accounts.delinquent[0]); + /// let vote_account_pubkey = &vote_account.vote_pubkey; + /// let vote_account_pubkey = Pubkey::from_str(vote_account_pubkey).expect("pubkey"); + /// + /// // Create a stake account + /// let stake_account = Keypair::new(); + /// let stake_account_pubkey = stake_account.pubkey(); + /// + /// // Build the instructions to create new stake account, + /// // funded by alice, and delegate to a validator's vote account. + /// let instrs = stake::instruction::create_account_and_delegate_stake( + /// &alice.pubkey(), + /// &stake_account_pubkey, + /// &vote_account_pubkey, + /// &Authorized::auto(&stake_account_pubkey), + /// &Lockup::default(), + /// 1_000_000, + /// ); + /// + /// let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// let tx = Transaction::new_signed_with_payer( + /// &instrs, + /// Some(&alice.pubkey()), + /// &[&alice, &stake_account], + /// latest_blockhash, + /// ); + /// + /// rpc_client.send_and_confirm_transaction(&tx)?; + /// + /// let epoch_info = rpc_client.get_epoch_info()?; + /// let activation = rpc_client.get_stake_activation( + /// stake_account_pubkey, + /// Some(epoch_info.epoch), + /// )?; + /// + /// assert_eq!(activation.state, StakeActivationState::Activating); + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_stake_activation( + &self, + stake_account: Pubkey, + epoch: Option, + ) -> ClientResult { + self.send( + RpcRequest::GetStakeActivation, + json!([ + stake_account.to_string(), + RpcEpochConfig { + epoch, + commitment: Some(self.commitment()), + } + ]), + ) + .await + } + + /// Returns information about the current supply. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSupply`] RPC method. + /// + /// [`getSupply`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsupply + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let supply = rpc_client.supply()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn supply(&self) -> RpcResult { + self.supply_with_commitment(self.commitment()).await + } + + /// Returns information about the current supply. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSupply`] RPC method. + /// + /// [`getSupply`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsupply + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::processed(); + /// let supply = rpc_client.supply_with_commitment( + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn supply_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> RpcResult { + self.send( + RpcRequest::GetSupply, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + } + + /// Returns the 20 largest accounts, by lamport balance. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getLargestAccounts`] RPC + /// method. + /// + /// [`getLargestAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getlargestaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # rpc_config::RpcLargestAccountsConfig, + /// # rpc_config::RpcLargestAccountsFilter, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::processed(); + /// let config = RpcLargestAccountsConfig { + /// commitment: Some(commitment_config), + /// filter: Some(RpcLargestAccountsFilter::Circulating), + /// }; + /// let accounts = rpc_client.get_largest_accounts_with_config( + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_largest_accounts_with_config( + &self, + config: RpcLargestAccountsConfig, + ) -> RpcResult> { + let commitment = config.commitment.unwrap_or_default(); + let commitment = self.maybe_map_commitment(commitment).await?; + let config = RpcLargestAccountsConfig { + commitment: Some(commitment), + ..config + }; + self.send(RpcRequest::GetLargestAccounts, json!([config])) + .await + } + + /// Returns the account info and associated stake for all the voting accounts + /// that have reached the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getVoteAccounts`] + /// RPC method. + /// + /// [`getVoteAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let accounts = rpc_client.get_vote_accounts()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_vote_accounts(&self) -> ClientResult { + self.get_vote_accounts_with_commitment(self.commitment()) + .await + } + + /// Returns the account info and associated stake for all the voting accounts + /// that have reached the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getVoteAccounts`] RPC method. + /// + /// [`getVoteAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::processed(); + /// let accounts = rpc_client.get_vote_accounts_with_commitment( + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_vote_accounts_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + ..RpcGetVoteAccountsConfig::default() + }) + .await + } + + /// Returns the account info and associated stake for all the voting accounts + /// that have reached the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getVoteAccounts`] RPC method. + /// + /// [`getVoteAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # rpc_config::RpcGetVoteAccountsConfig, + /// # }; + /// # use solana_sdk::{ + /// # signer::keypair::Keypair, + /// # signature::Signer, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let vote_keypair = Keypair::new(); + /// let vote_pubkey = vote_keypair.pubkey(); + /// let commitment = CommitmentConfig::processed(); + /// let config = RpcGetVoteAccountsConfig { + /// vote_pubkey: Some(vote_pubkey.to_string()), + /// commitment: Some(commitment), + /// keep_unstaked_delinquents: Some(true), + /// delinquent_slot_distance: Some(10), + /// }; + /// let accounts = rpc_client.get_vote_accounts_with_config( + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_vote_accounts_with_config( + &self, + config: RpcGetVoteAccountsConfig, + ) -> ClientResult { + self.send(RpcRequest::GetVoteAccounts, json!([config])) + .await + } + + pub async fn wait_for_max_stake( + &self, + commitment: CommitmentConfig, + max_stake_percent: f32, + ) -> ClientResult<()> { + let mut current_percent; + loop { + let vote_accounts = self.get_vote_accounts_with_commitment(commitment).await?; + + let mut max = 0; + let total_active_stake = vote_accounts + .current + .iter() + .chain(vote_accounts.delinquent.iter()) + .map(|vote_account| { + max = std::cmp::max(max, vote_account.activated_stake); + vote_account.activated_stake + }) + .sum::(); + current_percent = 100f32 * max as f32 / total_active_stake as f32; + if current_percent < max_stake_percent { + break; + } + info!( + "Waiting for stake to drop below {} current: {:.1}", + max_stake_percent, current_percent + ); + sleep(Duration::from_secs(10)).await; + } + Ok(()) + } + + /// Returns information about all the nodes participating in the cluster. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getClusterNodes`] + /// RPC method. + /// + /// [`getClusterNodes`]: https://docs.solana.com/developing/clients/jsonrpc-api#getclusternodes + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let cluster_nodes = rpc_client.get_cluster_nodes()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_cluster_nodes(&self) -> ClientResult> { + self.send(RpcRequest::GetClusterNodes, Value::Null).await + } + + /// Returns identity and transaction information about a confirmed block in the ledger. + /// + /// The encodings are returned in [`UiTransactionEncoding::Json`][uite] + /// format. To return transactions in other encodings, use + /// [`get_block_with_encoding`]. + /// + /// [`get_block_with_encoding`]: RpcClient::get_block_with_encoding + /// [uite]: UiTransactionEncoding::Json + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlock`] RPC + /// method. + /// + /// [`getBlock`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblock + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// let block = rpc_client.get_block(slot)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block(&self, slot: Slot) -> ClientResult { + self.get_block_with_encoding(slot, UiTransactionEncoding::Json) + .await + } + + /// Returns identity and transaction information about a confirmed block in the ledger. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlock`] RPC method. + /// + /// [`getBlock`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblock + /// + /// # Examples + /// + /// ``` + /// # use solana_transaction_status::UiTransactionEncoding; + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// let encoding = UiTransactionEncoding::Base58; + /// let block = rpc_client.get_block_with_encoding( + /// slot, + /// encoding, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_with_encoding( + &self, + slot: Slot, + encoding: UiTransactionEncoding, + ) -> ClientResult { + self.send( + self.maybe_map_request(RpcRequest::GetBlock).await?, + json!([slot, encoding]), + ) + .await + } + + /// Returns identity and transaction information about a confirmed block in the ledger. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlock`] RPC method. + /// + /// [`getBlock`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblock + /// + /// # Examples + /// + /// ``` + /// # use solana_transaction_status::{ + /// # TransactionDetails, + /// # UiTransactionEncoding, + /// # }; + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # rpc_config::RpcBlockConfig, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// let config = RpcBlockConfig { + /// encoding: Some(UiTransactionEncoding::Base58), + /// transaction_details: Some(TransactionDetails::None), + /// rewards: Some(true), + /// commitment: None, + /// }; + /// let block = rpc_client.get_block_with_config( + /// slot, + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_with_config( + &self, + slot: Slot, + config: RpcBlockConfig, + ) -> ClientResult { + self.send( + self.maybe_map_request(RpcRequest::GetBlock).await?, + json!([slot, config]), + ) + .await + } + + #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_block() instead")] + #[allow(deprecated)] + pub async fn get_confirmed_block(&self, slot: Slot) -> ClientResult { + self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_block_with_encoding() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_block_with_encoding( + &self, + slot: Slot, + encoding: UiTransactionEncoding, + ) -> ClientResult { + self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding])) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_block_with_config() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_block_with_config( + &self, + slot: Slot, + config: RpcConfirmedBlockConfig, + ) -> ClientResult { + self.send(RpcRequest::GetConfirmedBlock, json!([slot, config])) + .await + } + + /// Returns a list of finalized blocks between two slots. + /// + /// The range is inclusive, with results including the block for both + /// `start_slot` and `end_slot`. + /// + /// If `end_slot` is not provided, then the end slot is for the latest + /// finalized block. + /// + /// This method may not return blocks for the full range of slots if some + /// slots do not have corresponding blocks. To simply get a specific number + /// of sequential blocks, use the [`get_blocks_with_limit`] method. + /// + /// This method uses the [`Finalized`] [commitment level][cl]. + /// + /// [`Finalized`]: CommitmentLevel::Finalized + /// [`get_blocks_with_limit`]: RpcClient::get_blocks_with_limit. + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # Errors + /// + /// This method returns an error if the range is greater than 500,000 slots. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlocks`] RPC method, unless + /// the remote node version is less than 1.7, in which case it maps to the + /// [`getConfirmedBlocks`] RPC method. + /// + /// [`getBlocks`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblocks + /// [`getConfirmedBlocks`]: https://docs.solana.com/developing/clients/jsonrpc-api#getConfirmedblocks + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Get up to the first 10 blocks + /// let start_slot = 0; + /// let end_slot = 9; + /// let blocks = rpc_client.get_blocks(start_slot, Some(end_slot))?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_blocks( + &self, + start_slot: Slot, + end_slot: Option, + ) -> ClientResult> { + self.send( + self.maybe_map_request(RpcRequest::GetBlocks).await?, + json!([start_slot, end_slot]), + ) + .await + } + + /// Returns a list of confirmed blocks between two slots. + /// + /// The range is inclusive, with results including the block for both + /// `start_slot` and `end_slot`. + /// + /// If `end_slot` is not provided, then the end slot is for the latest + /// block with the given [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// This method may not return blocks for the full range of slots if some + /// slots do not have corresponding blocks. To simply get a specific number + /// of sequential blocks, use the [`get_blocks_with_limit_and_commitment`] + /// method. + /// + /// [`get_blocks_with_limit_and_commitment`]: RpcClient::get_blocks_with_limit_and_commitment. + /// + /// # Errors + /// + /// This method returns an error if the range is greater than 500,000 slots. + /// + /// This method returns an error if the given commitment level is below + /// [`Confirmed`]. + /// + /// [`Confirmed`]: CommitmentLevel::Confirmed + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlocks`] RPC method, unless + /// the remote node version is less than 1.7, in which case it maps to the + /// [`getConfirmedBlocks`] RPC method. + /// + /// [`getBlocks`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblocks + /// [`getConfirmedBlocks`]: https://docs.solana.com/developing/clients/jsonrpc-api#getConfirmedblocks + /// + /// # Examples + /// + /// ``` + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Get up to the first 10 blocks + /// let start_slot = 0; + /// let end_slot = 9; + /// // Method does not support commitment below `confirmed` + /// let commitment_config = CommitmentConfig::confirmed(); + /// let blocks = rpc_client.get_blocks_with_commitment( + /// start_slot, + /// Some(end_slot), + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_blocks_with_commitment( + &self, + start_slot: Slot, + end_slot: Option, + commitment_config: CommitmentConfig, + ) -> ClientResult> { + let json = if end_slot.is_some() { + json!([ + start_slot, + end_slot, + self.maybe_map_commitment(commitment_config).await? + ]) + } else { + json!([ + start_slot, + self.maybe_map_commitment(commitment_config).await? + ]) + }; + self.send(self.maybe_map_request(RpcRequest::GetBlocks).await?, json) + .await + } + + /// Returns a list of finalized blocks starting at the given slot. + /// + /// This method uses the [`Finalized`] [commitment level][cl]. + /// + /// [`Finalized`]: CommitmentLevel::Finalized. + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # Errors + /// + /// This method returns an error if the limit is greater than 500,000 slots. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlocksWithLimit`] RPC + /// method, unless the remote node version is less than 1.7, in which case + /// it maps to the [`getConfirmedBlocksWithLimit`] RPC method. + /// + /// [`getBlocksWithLimit`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockswithlimit + /// [`getConfirmedBlocksWithLimit`]: https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedblockswithlimit + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Get the first 10 blocks + /// let start_slot = 0; + /// let limit = 10; + /// let blocks = rpc_client.get_blocks_with_limit(start_slot, limit)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_blocks_with_limit( + &self, + start_slot: Slot, + limit: usize, + ) -> ClientResult> { + self.send( + self.maybe_map_request(RpcRequest::GetBlocksWithLimit) + .await?, + json!([start_slot, limit]), + ) + .await + } + + /// Returns a list of confirmed blocks starting at the given slot. + /// + /// # Errors + /// + /// This method returns an error if the limit is greater than 500,000 slots. + /// + /// This method returns an error if the given [commitment level][cl] is below + /// [`Confirmed`]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// [`Confirmed`]: CommitmentLevel::Confirmed + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlocksWithLimit`] RPC + /// method, unless the remote node version is less than 1.7, in which case + /// it maps to the `getConfirmedBlocksWithLimit` RPC method. + /// + /// [`getBlocksWithLimit`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblockswithlimit + /// [`getConfirmedBlocksWithLimit`]: https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedblockswithlimit + /// + /// # Examples + /// + /// ``` + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Get the first 10 blocks + /// let start_slot = 0; + /// let limit = 10; + /// let commitment_config = CommitmentConfig::confirmed(); + /// let blocks = rpc_client.get_blocks_with_limit_and_commitment( + /// start_slot, + /// limit, + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_blocks_with_limit_and_commitment( + &self, + start_slot: Slot, + limit: usize, + commitment_config: CommitmentConfig, + ) -> ClientResult> { + self.send( + self.maybe_map_request(RpcRequest::GetBlocksWithLimit) + .await?, + json!([ + start_slot, + limit, + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await + } + + #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_blocks() instead")] + #[allow(deprecated)] + pub async fn get_confirmed_blocks( + &self, + start_slot: Slot, + end_slot: Option, + ) -> ClientResult> { + self.send( + RpcRequest::GetConfirmedBlocks, + json!([start_slot, end_slot]), + ) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_blocks_with_commitment() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_blocks_with_commitment( + &self, + start_slot: Slot, + end_slot: Option, + commitment_config: CommitmentConfig, + ) -> ClientResult> { + let json = if end_slot.is_some() { + json!([ + start_slot, + end_slot, + self.maybe_map_commitment(commitment_config).await? + ]) + } else { + json!([ + start_slot, + self.maybe_map_commitment(commitment_config).await? + ]) + }; + self.send(RpcRequest::GetConfirmedBlocks, json).await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_blocks_with_limit() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_blocks_with_limit( + &self, + start_slot: Slot, + limit: usize, + ) -> ClientResult> { + self.send( + RpcRequest::GetConfirmedBlocksWithLimit, + json!([start_slot, limit]), + ) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_blocks_with_limit_and_commitment() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_blocks_with_limit_and_commitment( + &self, + start_slot: Slot, + limit: usize, + commitment_config: CommitmentConfig, + ) -> ClientResult> { + self.send( + RpcRequest::GetConfirmedBlocksWithLimit, + json!([ + start_slot, + limit, + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await + } + + /// Get confirmed signatures for transactions involving an address. + /// + /// Returns up to 1000 signatures, ordered from newest to oldest. + /// + /// This method uses the [`Finalized`] [commitment level][cl]. + /// + /// [`Finalized`]: CommitmentLevel::Finalized. + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSignaturesForAddress`] RPC + /// method, unless the remote node version is less than 1.7, in which case + /// it maps to the [`getSignaturesForAddress2`] RPC method. + /// + /// [`getSignaturesForAddress`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress + /// [`getSignaturesForAddress2`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress2 + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// let signatures = rpc_client.get_signatures_for_address( + /// &alice.pubkey(), + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signatures_for_address( + &self, + address: &Pubkey, + ) -> ClientResult> { + self.get_signatures_for_address_with_config( + address, + GetConfirmedSignaturesForAddress2Config::default(), + ) + .await + } + + /// Get confirmed signatures for transactions involving an address. + /// + /// # Errors + /// + /// This method returns an error if the given [commitment level][cl] is below + /// [`Confirmed`]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// [`Confirmed`]: CommitmentLevel::Confirmed + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getSignaturesForAddress`] RPC + /// method, unless the remote node version is less than 1.7, in which case + /// it maps to the [`getSignaturesForAddress2`] RPC method. + /// + /// [`getSignaturesForAddress`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress + /// [`getSignaturesForAddress2`]: https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress2 + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # rpc_client::GetConfirmedSignaturesForAddress2Config, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// # let signature = rpc_client.send_and_confirm_transaction(&tx)?; + /// let config = GetConfirmedSignaturesForAddress2Config { + /// before: None, + /// until: None, + /// limit: Some(3), + /// commitment: Some(CommitmentConfig::confirmed()), + /// }; + /// let signatures = rpc_client.get_signatures_for_address_with_config( + /// &alice.pubkey(), + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_signatures_for_address_with_config( + &self, + address: &Pubkey, + config: GetConfirmedSignaturesForAddress2Config, + ) -> ClientResult> { + let config = RpcSignaturesForAddressConfig { + before: config.before.map(|signature| signature.to_string()), + until: config.until.map(|signature| signature.to_string()), + limit: config.limit, + commitment: config.commitment, + }; + + let result: Vec = self + .send( + self.maybe_map_request(RpcRequest::GetSignaturesForAddress) + .await?, + json!([address.to_string(), config]), + ) + .await?; + + Ok(result) + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_signatures_for_address() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_signatures_for_address2( + &self, + address: &Pubkey, + ) -> ClientResult> { + self.get_confirmed_signatures_for_address2_with_config( + address, + GetConfirmedSignaturesForAddress2Config::default(), + ) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_signatures_for_address_with_config() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_signatures_for_address2_with_config( + &self, + address: &Pubkey, + config: GetConfirmedSignaturesForAddress2Config, + ) -> ClientResult> { + let config = RpcGetConfirmedSignaturesForAddress2Config { + before: config.before.map(|signature| signature.to_string()), + until: config.until.map(|signature| signature.to_string()), + limit: config.limit, + commitment: config.commitment, + }; + + let result: Vec = self + .send( + RpcRequest::GetConfirmedSignaturesForAddress2, + json!([address.to_string(), config]), + ) + .await?; + + Ok(result) + } + + /// Returns transaction details for a confirmed transaction. + /// + /// This method uses the [`Finalized`] [commitment level][cl]. + /// + /// [`Finalized`]: CommitmentLevel::Finalized + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getTransaction`] RPC method, + /// unless the remote node version is less than 1.7, in which case it maps + /// to the [`getConfirmedTransaction`] RPC method. + /// + /// [`getTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#gettransaction + /// [`getConfirmedTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedtransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # }; + /// # use solana_transaction_status::UiTransactionEncoding; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_and_confirm_transaction(&tx)?; + /// let transaction = rpc_client.get_transaction( + /// &signature, + /// UiTransactionEncoding::Json, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_transaction( + &self, + signature: &Signature, + encoding: UiTransactionEncoding, + ) -> ClientResult { + self.send( + self.maybe_map_request(RpcRequest::GetTransaction).await?, + json!([signature.to_string(), encoding]), + ) + .await + } + + /// Returns transaction details for a confirmed transaction. + /// + /// # Errors + /// + /// This method returns an error if the given [commitment level][cl] is below + /// [`Confirmed`]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// [`Confirmed`]: CommitmentLevel::Confirmed + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getTransaction`] RPC method, + /// unless the remote node version is less than 1.7, in which case it maps + /// to the [`getConfirmedTransaction`] RPC method. + /// + /// [`getTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#gettransaction + /// [`getConfirmedTransaction`]: https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedtransaction + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # rpc_config::RpcTransactionConfig, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signature::Signature, + /// # signer::keypair::Keypair, + /// # system_transaction, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # use solana_transaction_status::UiTransactionEncoding; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// # let lamports = 50; + /// # let latest_blockhash = rpc_client.get_latest_blockhash()?; + /// # let tx = system_transaction::transfer(&alice, &bob.pubkey(), lamports, latest_blockhash); + /// let signature = rpc_client.send_and_confirm_transaction(&tx)?; + /// let config = RpcTransactionConfig { + /// encoding: Some(UiTransactionEncoding::Json), + /// commitment: Some(CommitmentConfig::confirmed()), + /// }; + /// let transaction = rpc_client.get_transaction_with_config( + /// &signature, + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_transaction_with_config( + &self, + signature: &Signature, + config: RpcTransactionConfig, + ) -> ClientResult { + self.send( + self.maybe_map_request(RpcRequest::GetTransaction).await?, + json!([signature.to_string(), config]), + ) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_transaction() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_transaction( + &self, + signature: &Signature, + encoding: UiTransactionEncoding, + ) -> ClientResult { + self.send( + RpcRequest::GetConfirmedTransaction, + json!([signature.to_string(), encoding]), + ) + .await + } + + #[deprecated( + since = "1.7.0", + note = "Please use RpcClient::get_transaction_with_config() instead" + )] + #[allow(deprecated)] + pub async fn get_confirmed_transaction_with_config( + &self, + signature: &Signature, + config: RpcConfirmedTransactionConfig, + ) -> ClientResult { + self.send( + RpcRequest::GetConfirmedTransaction, + json!([signature.to_string(), config]), + ) + .await + } + + /// Returns the estimated production time of a block. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBlockTime`] RPC method. + /// + /// [`getBlockTime`]: https://docs.solana.com/developing/clients/jsonrpc-api#getblocktime + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// // Get the time of the most recent finalized block + /// let slot = rpc_client.get_slot()?; + /// let block_time = rpc_client.get_block_time(slot)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_block_time(&self, slot: Slot) -> ClientResult { + let request = RpcRequest::GetBlockTime; + let response = self.send(request, json!([slot])).await; + + response + .map(|result_json: Value| { + if result_json.is_null() { + return Err(RpcError::ForUser(format!("Block Not Found: slot={}", slot)).into()); + } + let result = serde_json::from_value(result_json) + .map_err(|err| ClientError::new_with_request(err.into(), request))?; + trace!("Response block timestamp {:?} {:?}", slot, result); + Ok(result) + }) + .map_err(|err| err.into_with_request(request))? + } + + /// Returns information about the current epoch. + /// + /// This method uses the configured default [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getEpochInfo`] RPC method. + /// + /// [`getEpochInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getepochinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let epoch_info = rpc_client.get_epoch_info()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_epoch_info(&self) -> ClientResult { + self.get_epoch_info_with_commitment(self.commitment()).await + } + + /// Returns information about the current epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getEpochInfo`] RPC method. + /// + /// [`getEpochInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getepochinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let commitment_config = CommitmentConfig::confirmed(); + /// let epoch_info = rpc_client.get_epoch_info_with_commitment( + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_epoch_info_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.send( + RpcRequest::GetEpochInfo, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + } + + /// Returns the leader schedule for an epoch. + /// + /// This method uses the configured default [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getLeaderSchedule`] RPC method. + /// + /// [`getLeaderSchedule`]: https://docs.solana.com/developing/clients/jsonrpc-api#getleaderschedule + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// let leader_schedule = rpc_client.get_leader_schedule( + /// Some(slot), + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_leader_schedule( + &self, + slot: Option, + ) -> ClientResult> { + self.get_leader_schedule_with_commitment(slot, self.commitment()) + .await + } + + /// Returns the leader schedule for an epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getLeaderSchedule`] RPC method. + /// + /// [`getLeaderSchedule`]: https://docs.solana.com/developing/clients/jsonrpc-api#getleaderschedule + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// let commitment_config = CommitmentConfig::processed(); + /// let leader_schedule = rpc_client.get_leader_schedule_with_commitment( + /// Some(slot), + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_leader_schedule_with_commitment( + &self, + slot: Option, + commitment_config: CommitmentConfig, + ) -> ClientResult> { + self.get_leader_schedule_with_config( + slot, + RpcLeaderScheduleConfig { + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + ..RpcLeaderScheduleConfig::default() + }, + ) + .await + } + + /// Returns the leader schedule for an epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getLeaderSchedule`] RPC method. + /// + /// [`getLeaderSchedule`]: https://docs.solana.com/developing/clients/jsonrpc-api#getleaderschedule + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_client::rpc_config::RpcLeaderScheduleConfig; + /// # use solana_sdk::commitment_config::CommitmentConfig; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let slot = rpc_client.get_slot()?; + /// # let validator_pubkey_str = "7AYmEYBBetok8h5L3Eo3vi3bDWnjNnaFbSXfSNYV5ewB".to_string(); + /// let config = RpcLeaderScheduleConfig { + /// identity: Some(validator_pubkey_str), + /// commitment: Some(CommitmentConfig::processed()), + /// }; + /// let leader_schedule = rpc_client.get_leader_schedule_with_config( + /// Some(slot), + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_leader_schedule_with_config( + &self, + slot: Option, + config: RpcLeaderScheduleConfig, + ) -> ClientResult> { + self.send(RpcRequest::GetLeaderSchedule, json!([slot, config])) + .await + } + + /// Returns epoch schedule information from this cluster's genesis config. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getEpochSchedule`] RPC method. + /// + /// [`getEpochSchedule`]: https://docs.solana.com/developing/clients/jsonrpc-api#getepochschedule + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let epoch_schedule = rpc_client.get_epoch_schedule()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_epoch_schedule(&self) -> ClientResult { + self.send(RpcRequest::GetEpochSchedule, Value::Null).await + } + + /// Returns a list of recent performance samples, in reverse slot order. + /// + /// Performance samples are taken every 60 seconds and include the number of + /// transactions and slots that occur in a given time window. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getRecentPerformanceSamples`] RPC method. + /// + /// [`getRecentPerformanceSamples`]: https://docs.solana.com/developing/clients/jsonrpc-api#getrecentperformancesamples + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let limit = 10; + /// let performance_samples = rpc_client.get_recent_performance_samples( + /// Some(limit), + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_recent_performance_samples( + &self, + limit: Option, + ) -> ClientResult> { + self.send(RpcRequest::GetRecentPerformanceSamples, json!([limit])) + .await + } + + /// Returns the identity pubkey for the current node. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getIdentity`] RPC method. + /// + /// [`getIdentity`]: https://docs.solana.com/developing/clients/jsonrpc-api#getidentity + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let identity = rpc_client.get_identity()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_identity(&self) -> ClientResult { + let rpc_identity: RpcIdentity = self.send(RpcRequest::GetIdentity, Value::Null).await?; + + rpc_identity.identity.parse::().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Pubkey".to_string()).into(), + RpcRequest::GetIdentity, + ) + }) + } + + /// Returns the current inflation governor. + /// + /// This method uses the [`Finalized`] [commitment level][cl]. + /// + /// [`Finalized`]: CommitmentLevel::Finalized + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getInflationGovernor`] RPC + /// method. + /// + /// [`getInflationGovernor`]: https://docs.solana.com/developing/clients/jsonrpc-api#getinflationgovernor + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let inflation_governor = rpc_client.get_inflation_governor()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_inflation_governor(&self) -> ClientResult { + self.send(RpcRequest::GetInflationGovernor, Value::Null) + .await + } + + /// Returns the specific inflation values for the current epoch. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getInflationRate`] RPC method. + /// + /// [`getInflationRate`]: https://docs.solana.com/developing/clients/jsonrpc-api#getinflationrate + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let inflation_rate = rpc_client.get_inflation_rate()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_inflation_rate(&self) -> ClientResult { + self.send(RpcRequest::GetInflationRate, Value::Null).await + } + + /// Returns the inflation reward for a list of addresses for an epoch. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getInflationReward`] RPC method. + /// + /// [`getInflationReward`]: https://docs.solana.com/developing/clients/jsonrpc-api#getinflationreward + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::signature::{Keypair, Signer}; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let epoch_info = rpc_client.get_epoch_info()?; + /// # let epoch = epoch_info.epoch; + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// let addresses = vec![alice.pubkey(), bob.pubkey()]; + /// let inflation_reward = rpc_client.get_inflation_reward( + /// &addresses, + /// Some(epoch), + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_inflation_reward( + &self, + addresses: &[Pubkey], + epoch: Option, + ) -> ClientResult>> { + let addresses: Vec<_> = addresses + .iter() + .map(|address| address.to_string()) + .collect(); + self.send( + RpcRequest::GetInflationReward, + json!([ + addresses, + RpcEpochConfig { + epoch, + commitment: Some(self.commitment()), + } + ]), + ) + .await + } + + /// Returns the current solana version running on the node. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getVersion`] RPC method. + /// + /// [`getVersion`]: https://docs.solana.com/developing/clients/jsonrpc-api#getversion + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # use solana_sdk::signature::{Keypair, Signer}; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let expected_version = semver::Version::new(1, 7, 0); + /// let version = rpc_client.get_version()?; + /// let version = semver::Version::parse(&version.solana_core)?; + /// assert!(version >= expected_version); + /// # Ok::<(), Box>(()) + /// ``` + pub async fn get_version(&self) -> ClientResult { + self.send(RpcRequest::GetVersion, Value::Null).await + } + + /// Returns the lowest slot that the node has information about in its ledger. + /// + /// This value may increase over time if the node is configured to purge + /// older ledger data. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`minimumLedgerSlot`] RPC + /// method. + /// + /// [`minimumLedgerSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#minimumledgerslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # client_error::ClientError, + /// # rpc_client::RpcClient, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let slot = rpc_client.minimum_ledger_slot()?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn minimum_ledger_slot(&self) -> ClientResult { + self.send(RpcRequest::MinimumLedgerSlot, Value::Null).await + } + + /// Returns all information associated with the account of the provided pubkey. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// To get multiple accounts at once, use the [`get_multiple_accounts`] method. + /// + /// [`get_multiple_accounts`]: RpcClient::get_multiple_accounts + /// + /// # Errors + /// + /// If the account does not exist, this method returns + /// [`RpcError::ForUser`]. This is unlike [`get_account_with_commitment`], + /// which returns `Ok(None)` if the account does not exist. + /// + /// [`get_account_with_commitment`]: RpcClient::get_account_with_commitment + /// + /// # RPC Reference + /// + /// This method is built on the [`getAccountInfo`] RPC method. + /// + /// [`getAccountInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::{self, RpcClient}, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # pubkey::Pubkey, + /// # }; + /// # use std::str::FromStr; + /// # let mocks = rpc_client::create_rpc_client_mocks(); + /// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks); + /// let alice_pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap(); + /// let account = rpc_client.get_account(&alice_pubkey)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_account(&self, pubkey: &Pubkey) -> ClientResult { + self.get_account_with_commitment(pubkey, self.commitment()) + .await? + .value + .ok_or_else(|| RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into()) + } + + /// Returns all information associated with the account of the provided pubkey. + /// + /// If the account does not exist, this method returns `Ok(None)`. + /// + /// To get multiple accounts at once, use the [`get_multiple_accounts_with_commitment`] method. + /// + /// [`get_multiple_accounts_with_commitment`]: RpcClient::get_multiple_accounts_with_commitment + /// + /// # RPC Reference + /// + /// This method is built on the [`getAccountInfo`] RPC method. + /// + /// [`getAccountInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::{self, RpcClient}, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # pubkey::Pubkey, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # use std::str::FromStr; + /// # let mocks = rpc_client::create_rpc_client_mocks(); + /// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks); + /// let alice_pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap(); + /// let commitment_config = CommitmentConfig::processed(); + /// let account = rpc_client.get_account_with_commitment( + /// &alice_pubkey, + /// commitment_config, + /// )?; + /// assert!(account.value.is_some()); + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_account_with_commitment( + &self, + pubkey: &Pubkey, + commitment_config: CommitmentConfig, + ) -> RpcResult> { + let config = RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + data_slice: None, + }; + let response = self + .send( + RpcRequest::GetAccountInfo, + json!([pubkey.to_string(), config]), + ) + .await; + + response + .map(|result_json: Value| { + if result_json.is_null() { + return Err( + RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(), + ); + } + let Response { + context, + value: rpc_account, + } = serde_json::from_value::>>(result_json)?; + trace!("Response account {:?} {:?}", pubkey, rpc_account); + let account = rpc_account.and_then(|rpc_account| rpc_account.decode()); + + Ok(Response { + context, + value: account, + }) + }) + .map_err(|err| { + Into::::into(RpcError::ForUser(format!( + "AccountNotFound: pubkey={}: {}", + pubkey, err + ))) + })? + } + + /// Get the max slot seen from retransmit stage. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getMaxRetransmitSlot`] RPC + /// method. + /// + /// [`getMaxRetransmitSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#getmaxretransmitslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let slot = rpc_client.get_max_retransmit_slot()?; + /// # Ok::<(), ClientError>(()) + pub async fn get_max_retransmit_slot(&self) -> ClientResult { + self.send(RpcRequest::GetMaxRetransmitSlot, Value::Null) + .await + } + + /// Get the max slot seen from after [shred](https://docs.solana.com/terminology#shred) insert. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the + /// [`getMaxShredInsertSlot`] RPC method. + /// + /// [`getMaxShredInsertSlot`]: https://docs.solana.com/developing/clients/jsonrpc-api#getmaxshredinsertslot + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let slot = rpc_client.get_max_shred_insert_slot()?; + /// # Ok::<(), ClientError>(()) + pub async fn get_max_shred_insert_slot(&self) -> ClientResult { + self.send(RpcRequest::GetMaxShredInsertSlot, Value::Null) + .await + } + + /// Returns the account information for a list of pubkeys. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method is built on the [`getMultipleAccounts`] RPC method. + /// + /// [`getMultipleAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getmultipleaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// let pubkeys = vec![alice.pubkey(), bob.pubkey()]; + /// let accounts = rpc_client.get_multiple_accounts(&pubkeys)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_multiple_accounts( + &self, + pubkeys: &[Pubkey], + ) -> ClientResult>> { + Ok(self + .get_multiple_accounts_with_commitment(pubkeys, self.commitment()) + .await? + .value) + } + + /// Returns the account information for a list of pubkeys. + /// + /// # RPC Reference + /// + /// This method is built on the [`getMultipleAccounts`] RPC method. + /// + /// [`getMultipleAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getmultipleaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// let pubkeys = vec![alice.pubkey(), bob.pubkey()]; + /// let commitment_config = CommitmentConfig::processed(); + /// let accounts = rpc_client.get_multiple_accounts_with_commitment( + /// &pubkeys, + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_multiple_accounts_with_commitment( + &self, + pubkeys: &[Pubkey], + commitment_config: CommitmentConfig, + ) -> RpcResult>> { + self.get_multiple_accounts_with_config( + pubkeys, + RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + data_slice: None, + }, + ) + .await + } + + /// Returns the account information for a list of pubkeys. + /// + /// # RPC Reference + /// + /// This method is built on the [`getMultipleAccounts`] RPC method. + /// + /// [`getMultipleAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getmultipleaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # rpc_config::RpcAccountInfoConfig, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # use solana_account_decoder::UiAccountEncoding; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let bob = Keypair::new(); + /// let pubkeys = vec![alice.pubkey(), bob.pubkey()]; + /// let commitment_config = CommitmentConfig::processed(); + /// let config = RpcAccountInfoConfig { + /// encoding: Some(UiAccountEncoding::Base64), + /// commitment: Some(commitment_config), + /// .. RpcAccountInfoConfig::default() + /// }; + /// let accounts = rpc_client.get_multiple_accounts_with_config( + /// &pubkeys, + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_multiple_accounts_with_config( + &self, + pubkeys: &[Pubkey], + config: RpcAccountInfoConfig, + ) -> RpcResult>> { + let config = RpcAccountInfoConfig { + commitment: config.commitment.or_else(|| Some(self.commitment())), + ..config + }; + let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect(); + let response = self + .send(RpcRequest::GetMultipleAccounts, json!([pubkeys, config])) + .await?; + let Response { + context, + value: accounts, + } = serde_json::from_value::>>>(response)?; + let accounts: Vec> = accounts + .into_iter() + .map(|rpc_account| rpc_account.and_then(|a| a.decode())) + .collect(); + Ok(Response { + context, + value: accounts, + }) + } + + /// Gets the raw data associated with an account. + /// + /// This is equivalent to calling [`get_account`] and then accessing the + /// [`data`] field of the returned [`Account`]. + /// + /// [`get_account`]: RpcClient::get_account + /// [`data`]: Account::data + /// + /// # RPC Reference + /// + /// This method is built on the [`getAccountInfo`] RPC method. + /// + /// [`getAccountInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::{self, RpcClient}, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # pubkey::Pubkey, + /// # }; + /// # use std::str::FromStr; + /// # let mocks = rpc_client::create_rpc_client_mocks(); + /// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks); + /// let alice_pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap(); + /// let account_data = rpc_client.get_account_data(&alice_pubkey)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_account_data(&self, pubkey: &Pubkey) -> ClientResult> { + Ok(self.get_account(pubkey).await?.data) + } + + /// Returns minimum balance required to make an account with specified data length rent exempt. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the + /// [`getMinimumBalanceForRentExemption`] RPC method. + /// + /// [`getMinimumBalanceForRentExemption`]: https://docs.solana.com/developing/clients/jsonrpc-api#getminimumbalanceforrentexemption + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// let data_len = 300; + /// let balance = rpc_client.get_minimum_balance_for_rent_exemption(data_len)?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_minimum_balance_for_rent_exemption( + &self, + data_len: usize, + ) -> ClientResult { + let request = RpcRequest::GetMinimumBalanceForRentExemption; + let minimum_balance_json: Value = self + .send(request, json!([data_len])) + .await + .map_err(|err| err.into_with_request(request))?; + + let minimum_balance: u64 = serde_json::from_value(minimum_balance_json) + .map_err(|err| ClientError::new_with_request(err.into(), request))?; + trace!( + "Response minimum balance {:?} {:?}", + data_len, + minimum_balance + ); + Ok(minimum_balance) + } + + /// Request the balance of the provided account pubkey. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBalance`] RPC method. + /// + /// [`getBalance`]: https://docs.solana.com/developing/clients/jsonrpc-api#getbalance + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// let balance = rpc_client.get_balance(&alice.pubkey())?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_balance(&self, pubkey: &Pubkey) -> ClientResult { + Ok(self + .get_balance_with_commitment(pubkey, self.commitment()) + .await? + .value) + } + + /// Request the balance of the provided account pubkey. + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getBalance`] RPC method. + /// + /// [`getBalance`]: https://docs.solana.com/developing/clients/jsonrpc-api#getbalance + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// let commitment_config = CommitmentConfig::processed(); + /// let balance = rpc_client.get_balance_with_commitment( + /// &alice.pubkey(), + /// commitment_config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_balance_with_commitment( + &self, + pubkey: &Pubkey, + commitment_config: CommitmentConfig, + ) -> RpcResult { + self.send( + RpcRequest::GetBalance, + json!([ + pubkey.to_string(), + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await + } + + /// Returns all accounts owned by the provided program pubkey. + /// + /// This method uses the configured [commitment level][cl]. + /// + /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment + /// + /// # RPC Reference + /// + /// This method corresponds directly to the [`getProgramAccounts`] RPC + /// method. + /// + /// [`getProgramAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # }; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// let accounts = rpc_client.get_program_accounts(&alice.pubkey())?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_program_accounts( + &self, + pubkey: &Pubkey, + ) -> ClientResult> { + self.get_program_accounts_with_config( + pubkey, + RpcProgramAccountsConfig { + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + ..RpcAccountInfoConfig::default() + }, + ..RpcProgramAccountsConfig::default() + }, + ) + .await + } + + /// Returns all accounts owned by the provided program pubkey. + /// + /// # RPC Reference + /// + /// This method is built on the [`getProgramAccounts`] RPC method. + /// + /// [`getProgramAccounts`]: https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts + /// + /// # Examples + /// + /// ``` + /// # use solana_client::{ + /// # rpc_client::RpcClient, + /// # client_error::ClientError, + /// # rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + /// # rpc_filter::{MemcmpEncodedBytes, RpcFilterType, Memcmp}, + /// # }; + /// # use solana_sdk::{ + /// # signature::Signer, + /// # signer::keypair::Keypair, + /// # commitment_config::CommitmentConfig, + /// # }; + /// # use solana_account_decoder::{UiDataSliceConfig, UiAccountEncoding}; + /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); + /// # let alice = Keypair::new(); + /// # let base64_bytes = "\ + /// # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + /// # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + /// let memcmp = RpcFilterType::Memcmp(Memcmp { + /// offset: 0, + /// bytes: MemcmpEncodedBytes::Base64(base64_bytes.to_string()), + /// encoding: None, + /// }); + /// let config = RpcProgramAccountsConfig { + /// filters: Some(vec![ + /// RpcFilterType::DataSize(128), + /// memcmp, + /// ]), + /// account_config: RpcAccountInfoConfig { + /// encoding: Some(UiAccountEncoding::Base64), + /// data_slice: Some(UiDataSliceConfig { + /// offset: 0, + /// length: 5, + /// }), + /// commitment: Some(CommitmentConfig::processed()), + /// }, + /// with_context: Some(false), + /// }; + /// let accounts = rpc_client.get_program_accounts_with_config( + /// &alice.pubkey(), + /// config, + /// )?; + /// # Ok::<(), ClientError>(()) + /// ``` + pub async fn get_program_accounts_with_config( + &self, + pubkey: &Pubkey, + config: RpcProgramAccountsConfig, + ) -> ClientResult> { + let commitment = config + .account_config + .commitment + .unwrap_or_else(|| self.commitment()); + let commitment = self.maybe_map_commitment(commitment).await?; + let account_config = RpcAccountInfoConfig { + commitment: Some(commitment), + ..config.account_config + }; + let config = RpcProgramAccountsConfig { + account_config, + ..config + }; + let accounts: Vec = self + .send( + RpcRequest::GetProgramAccounts, + json!([pubkey.to_string(), config]), + ) + .await?; + parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts) + } + + /// Request the transaction count. + pub async fn get_transaction_count(&self) -> ClientResult { + self.get_transaction_count_with_commitment(self.commitment()) + .await + } + + pub async fn get_transaction_count_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.send( + RpcRequest::GetTransactionCount, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + } + + #[deprecated( + since = "1.9.0", + note = "Please use `get_latest_blockhash` and `get_fee_for_message` instead" + )] + #[allow(deprecated)] + pub async fn get_fees(&self) -> ClientResult { + #[allow(deprecated)] + Ok(self + .get_fees_with_commitment(self.commitment()) + .await? + .value) + } + + #[deprecated( + since = "1.9.0", + note = "Please use `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" + )] + #[allow(deprecated)] + pub async fn get_fees_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> RpcResult { + let Response { + context, + value: fees, + } = self + .send::>( + RpcRequest::GetFees, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await?; + let blockhash = fees.blockhash.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Hash".to_string()).into(), + RpcRequest::GetFees, + ) + })?; + Ok(Response { + context, + value: Fees { + blockhash, + fee_calculator: fees.fee_calculator, + last_valid_block_height: fees.last_valid_block_height, + }, + }) + } + + #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] + #[allow(deprecated)] + pub async fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { + #[allow(deprecated)] + let (blockhash, fee_calculator, _last_valid_slot) = self + .get_recent_blockhash_with_commitment(self.commitment()) + .await? + .value; + Ok((blockhash, fee_calculator)) + } + + #[deprecated( + since = "1.9.0", + note = "Please use `get_latest_blockhash_with_commitment` instead" + )] + #[allow(deprecated)] + pub async fn get_recent_blockhash_with_commitment( + &self, + commitment_config: CommitmentConfig, + ) -> RpcResult<(Hash, FeeCalculator, Slot)> { + let (context, blockhash, fee_calculator, last_valid_slot) = if let Ok(Response { + context, + value: + RpcFees { + blockhash, + fee_calculator, + last_valid_slot, + .. + }, + }) = self + .send::>( + RpcRequest::GetFees, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + { + (context, blockhash, fee_calculator, last_valid_slot) + } else if let Ok(Response { + context, + value: + DeprecatedRpcFees { + blockhash, + fee_calculator, + last_valid_slot, + }, + }) = self + .send::>( + RpcRequest::GetFees, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + { + (context, blockhash, fee_calculator, last_valid_slot) + } else if let Ok(Response { + context, + value: + RpcBlockhashFeeCalculator { + blockhash, + fee_calculator, + }, + }) = self + .send::>( + RpcRequest::GetRecentBlockhash, + json!([self.maybe_map_commitment(commitment_config).await?]), + ) + .await + { + (context, blockhash, fee_calculator, 0) + } else { + return Err(ClientError::new_with_request( + RpcError::ParseError("RpcBlockhashFeeCalculator or RpcFees".to_string()).into(), + RpcRequest::GetRecentBlockhash, + )); + }; + + let blockhash = blockhash.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Hash".to_string()).into(), + RpcRequest::GetRecentBlockhash, + ) + })?; + Ok(Response { + context, + value: (blockhash, fee_calculator, last_valid_slot), + }) + } + + #[deprecated(since = "1.9.0", note = "Please `get_fee_for_message` instead")] + #[allow(deprecated)] + pub async fn get_fee_calculator_for_blockhash( + &self, + blockhash: &Hash, + ) -> ClientResult> { + #[allow(deprecated)] + Ok(self + .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment()) + .await? + .value) + } + + #[deprecated( + since = "1.9.0", + note = "Please `get_latest_blockhash_with_commitment` and `get_fee_for_message` instead" + )] + #[allow(deprecated)] + pub async fn get_fee_calculator_for_blockhash_with_commitment( + &self, + blockhash: &Hash, + commitment_config: CommitmentConfig, + ) -> RpcResult> { + let Response { context, value } = self + .send::>>( + RpcRequest::GetFeeCalculatorForBlockhash, + json!([ + blockhash.to_string(), + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await?; + + Ok(Response { + context, + value: value.map(|rf| rf.fee_calculator), + }) + } + + #[deprecated( + since = "1.9.0", + note = "Please do not use, will no longer be available in the future" + )] + #[allow(deprecated)] + pub async fn get_fee_rate_governor(&self) -> RpcResult { + let Response { + context, + value: RpcFeeRateGovernor { fee_rate_governor }, + } = self + .send::>(RpcRequest::GetFeeRateGovernor, Value::Null) + .await?; + + Ok(Response { + context, + value: fee_rate_governor, + }) + } + + #[deprecated( + since = "1.9.0", + note = "Please do not use, will no longer be available in the future" + )] + #[allow(deprecated)] + pub async fn get_new_blockhash(&self, blockhash: &Hash) -> ClientResult<(Hash, FeeCalculator)> { + let mut num_retries = 0; + let start = Instant::now(); + while start.elapsed().as_secs() < 5 { + #[allow(deprecated)] + if let Ok((new_blockhash, fee_calculator)) = self.get_recent_blockhash().await { + if new_blockhash != *blockhash { + return Ok((new_blockhash, fee_calculator)); + } + } + debug!("Got same blockhash ({:?}), will retry...", blockhash); + + // Retry ~twice during a slot + sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)).await; + num_retries += 1; + } + Err(RpcError::ForUser(format!( + "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", + start.elapsed().as_millis(), + num_retries, + blockhash + )) + .into()) + } + + pub async fn get_first_available_block(&self) -> ClientResult { + self.send(RpcRequest::GetFirstAvailableBlock, Value::Null) + .await + } + + pub async fn get_genesis_hash(&self) -> ClientResult { + let hash_str: String = self.send(RpcRequest::GetGenesisHash, Value::Null).await?; + let hash = hash_str.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Hash".to_string()).into(), + RpcRequest::GetGenesisHash, + ) + })?; + Ok(hash) + } + + pub async fn get_health(&self) -> ClientResult<()> { + self.send::(RpcRequest::GetHealth, Value::Null) + .await + .map(|_| ()) + } + + pub async fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult> { + Ok(self + .get_token_account_with_commitment(pubkey, self.commitment()) + .await? + .value) + } + + pub async fn get_token_account_with_commitment( + &self, + pubkey: &Pubkey, + commitment_config: CommitmentConfig, + ) -> RpcResult> { + let config = RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::JsonParsed), + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + data_slice: None, + }; + let response = self + .send( + RpcRequest::GetAccountInfo, + json!([pubkey.to_string(), config]), + ) + .await; + + response + .map(|result_json: Value| { + if result_json.is_null() { + return Err( + RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(), + ); + } + let Response { + context, + value: rpc_account, + } = serde_json::from_value::>>(result_json)?; + trace!("Response account {:?} {:?}", pubkey, rpc_account); + let response = { + if let Some(rpc_account) = rpc_account { + if let UiAccountData::Json(account_data) = rpc_account.data { + let token_account_type: TokenAccountType = + serde_json::from_value(account_data.parsed)?; + if let TokenAccountType::Account(token_account) = token_account_type { + return Ok(Response { + context, + value: Some(token_account), + }); + } + } + } + Err(Into::::into(RpcError::ForUser(format!( + "Account could not be parsed as token account: pubkey={}", + pubkey + )))) + }; + response? + }) + .map_err(|err| { + Into::::into(RpcError::ForUser(format!( + "AccountNotFound: pubkey={}: {}", + pubkey, err + ))) + })? + } + + pub async fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult { + Ok(self + .get_token_account_balance_with_commitment(pubkey, self.commitment()) + .await? + .value) + } + + pub async fn get_token_account_balance_with_commitment( + &self, + pubkey: &Pubkey, + commitment_config: CommitmentConfig, + ) -> RpcResult { + self.send( + RpcRequest::GetTokenAccountBalance, + json!([ + pubkey.to_string(), + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await + } + + pub async fn get_token_accounts_by_delegate( + &self, + delegate: &Pubkey, + token_account_filter: TokenAccountsFilter, + ) -> ClientResult> { + Ok(self + .get_token_accounts_by_delegate_with_commitment( + delegate, + token_account_filter, + self.commitment(), + ) + .await? + .value) + } + + pub async fn get_token_accounts_by_delegate_with_commitment( + &self, + delegate: &Pubkey, + token_account_filter: TokenAccountsFilter, + commitment_config: CommitmentConfig, + ) -> RpcResult> { + let token_account_filter = match token_account_filter { + TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()), + TokenAccountsFilter::ProgramId(program_id) => { + RpcTokenAccountsFilter::ProgramId(program_id.to_string()) + } + }; + + let config = RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::JsonParsed), + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + data_slice: None, + }; + + self.send( + RpcRequest::GetTokenAccountsByOwner, + json!([delegate.to_string(), token_account_filter, config]), + ) + .await + } + + pub async fn get_token_accounts_by_owner( + &self, + owner: &Pubkey, + token_account_filter: TokenAccountsFilter, + ) -> ClientResult> { + Ok(self + .get_token_accounts_by_owner_with_commitment( + owner, + token_account_filter, + self.commitment(), + ) + .await? + .value) + } + + pub async fn get_token_accounts_by_owner_with_commitment( + &self, + owner: &Pubkey, + token_account_filter: TokenAccountsFilter, + commitment_config: CommitmentConfig, + ) -> RpcResult> { + let token_account_filter = match token_account_filter { + TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()), + TokenAccountsFilter::ProgramId(program_id) => { + RpcTokenAccountsFilter::ProgramId(program_id.to_string()) + } + }; + + let config = RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::JsonParsed), + commitment: Some(self.maybe_map_commitment(commitment_config).await?), + data_slice: None, + }; + + self.send( + RpcRequest::GetTokenAccountsByOwner, + json!([owner.to_string(), token_account_filter, config]), + ) + .await + } + + pub async fn get_token_supply(&self, mint: &Pubkey) -> ClientResult { + Ok(self + .get_token_supply_with_commitment(mint, self.commitment()) + .await? + .value) + } + + pub async fn get_token_supply_with_commitment( + &self, + mint: &Pubkey, + commitment_config: CommitmentConfig, + ) -> RpcResult { + self.send( + RpcRequest::GetTokenSupply, + json!([ + mint.to_string(), + self.maybe_map_commitment(commitment_config).await? + ]), + ) + .await + } + + pub async fn request_airdrop(&self, pubkey: &Pubkey, lamports: u64) -> ClientResult { + self.request_airdrop_with_config( + pubkey, + lamports, + RpcRequestAirdropConfig { + commitment: Some(self.commitment()), + ..RpcRequestAirdropConfig::default() + }, + ) + .await + } + + pub async fn request_airdrop_with_blockhash( + &self, + pubkey: &Pubkey, + lamports: u64, + recent_blockhash: &Hash, + ) -> ClientResult { + self.request_airdrop_with_config( + pubkey, + lamports, + RpcRequestAirdropConfig { + commitment: Some(self.commitment()), + recent_blockhash: Some(recent_blockhash.to_string()), + }, + ) + .await + } + + pub async fn request_airdrop_with_config( + &self, + pubkey: &Pubkey, + lamports: u64, + config: RpcRequestAirdropConfig, + ) -> ClientResult { + let commitment = config.commitment.unwrap_or_default(); + let commitment = self.maybe_map_commitment(commitment).await?; + let config = RpcRequestAirdropConfig { + commitment: Some(commitment), + ..config + }; + self.send( + RpcRequest::RequestAirdrop, + json!([pubkey.to_string(), lamports, config]), + ) + .await + .and_then(|signature: String| { + Signature::from_str(&signature).map_err(|err| { + ClientErrorKind::Custom(format!("signature deserialization failed: {}", err)).into() + }) + }) + .map_err(|_| { + RpcError::ForUser( + "airdrop request failed. \ + This can happen when the rate limit is reached." + .to_string(), + ) + .into() + }) + } + + pub(crate) async fn poll_balance_with_timeout_and_commitment( + &self, + pubkey: &Pubkey, + polling_frequency: &Duration, + timeout: &Duration, + commitment_config: CommitmentConfig, + ) -> ClientResult { + let now = Instant::now(); + loop { + match self + .get_balance_with_commitment(pubkey, commitment_config) + .await + { + Ok(bal) => { + return Ok(bal.value); + } + Err(e) => { + sleep(*polling_frequency).await; + if now.elapsed() > *timeout { + return Err(e); + } + } + }; + } + } + + pub async fn poll_get_balance_with_commitment( + &self, + pubkey: &Pubkey, + commitment_config: CommitmentConfig, + ) -> ClientResult { + self.poll_balance_with_timeout_and_commitment( + pubkey, + &Duration::from_millis(100), + &Duration::from_secs(1), + commitment_config, + ) + .await + } + + pub async fn wait_for_balance_with_commitment( + &self, + pubkey: &Pubkey, + expected_balance: Option, + commitment_config: CommitmentConfig, + ) -> ClientResult { + const LAST: usize = 30; + let mut run = 0; + loop { + let balance_result = self + .poll_get_balance_with_commitment(pubkey, commitment_config) + .await; + if expected_balance.is_none() || (balance_result.is_err() && run == LAST) { + return balance_result; + } + trace!( + "wait_for_balance_with_commitment [{}] {:?} {:?}", + run, + balance_result, + expected_balance + ); + if let (Some(expected_balance), Ok(balance_result)) = (expected_balance, balance_result) + { + if expected_balance == balance_result { + return Ok(balance_result); + } + } + run += 1; + } + } + + /// Poll the server to confirm a transaction. + pub async fn poll_for_signature(&self, signature: &Signature) -> ClientResult<()> { + self.poll_for_signature_with_commitment(signature, self.commitment()) + .await + } + + /// Poll the server to confirm a transaction. + pub async fn poll_for_signature_with_commitment( + &self, + signature: &Signature, + commitment_config: CommitmentConfig, + ) -> ClientResult<()> { + let now = Instant::now(); + loop { + if let Ok(Some(_)) = self + .get_signature_status_with_commitment(signature, commitment_config) + .await + { + break; + } + if now.elapsed().as_secs() > 15 { + return Err(RpcError::ForUser(format!( + "signature not found after {} seconds", + now.elapsed().as_secs() + )) + .into()); + } + sleep(Duration::from_millis(250)).await; + } + Ok(()) + } + + /// Poll the server to confirm a transaction. + pub async fn poll_for_signature_confirmation( + &self, + signature: &Signature, + min_confirmed_blocks: usize, + ) -> ClientResult { + let mut now = Instant::now(); + let mut confirmed_blocks = 0; + loop { + let response = self + .get_num_blocks_since_signature_confirmation(signature) + .await; + match response { + Ok(count) => { + if confirmed_blocks != count { + info!( + "signature {} confirmed {} out of {} after {} ms", + signature, + count, + min_confirmed_blocks, + now.elapsed().as_millis() + ); + now = Instant::now(); + confirmed_blocks = count; + } + if count >= min_confirmed_blocks { + break; + } + } + Err(err) => { + debug!("check_confirmations request failed: {:?}", err); + } + }; + if now.elapsed().as_secs() > 20 { + info!( + "signature {} confirmed {} out of {} failed after {} ms", + signature, + confirmed_blocks, + min_confirmed_blocks, + now.elapsed().as_millis() + ); + if confirmed_blocks > 0 { + return Ok(confirmed_blocks); + } else { + return Err(RpcError::ForUser(format!( + "signature not found after {} seconds", + now.elapsed().as_secs() + )) + .into()); + } + } + sleep(Duration::from_millis(250)).await; + } + Ok(confirmed_blocks) + } + + pub async fn get_num_blocks_since_signature_confirmation( + &self, + signature: &Signature, + ) -> ClientResult { + let result: Response>> = self + .send( + RpcRequest::GetSignatureStatuses, + json!([[signature.to_string()]]), + ) + .await?; + + let confirmations = result.value[0] + .clone() + .ok_or_else(|| { + ClientError::new_with_request( + ClientErrorKind::Custom("signature not found".to_string()), + RpcRequest::GetSignatureStatuses, + ) + })? + .confirmations + .unwrap_or(MAX_LOCKOUT_HISTORY + 1); + Ok(confirmations) + } + + pub async fn get_latest_blockhash(&self) -> ClientResult { + let (blockhash, _) = self + .get_latest_blockhash_with_commitment(self.commitment()) + .await?; + Ok(blockhash) + } + + #[allow(deprecated)] + pub async fn get_latest_blockhash_with_commitment( + &self, + commitment: CommitmentConfig, + ) -> ClientResult<(Hash, u64)> { + let (blockhash, last_valid_block_height) = + if self.get_node_version().await? < semver::Version::new(1, 9, 0) { + let Fees { + blockhash, + last_valid_block_height, + .. + } = self.get_fees_with_commitment(commitment).await?.value; + (blockhash, last_valid_block_height) + } else { + let RpcBlockhash { + blockhash, + last_valid_block_height, + } = self + .send::>( + RpcRequest::GetLatestBlockhash, + json!([self.maybe_map_commitment(commitment).await?]), + ) + .await? + .value; + let blockhash = blockhash.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Hash".to_string()).into(), + RpcRequest::GetLatestBlockhash, + ) + })?; + (blockhash, last_valid_block_height) + }; + Ok((blockhash, last_valid_block_height)) + } + + #[allow(deprecated)] + pub async fn is_blockhash_valid( + &self, + blockhash: &Hash, + commitment: CommitmentConfig, + ) -> ClientResult { + let result = if self.get_node_version().await? < semver::Version::new(1, 9, 0) { + self.get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment) + .await? + .value + .is_some() + } else { + self.send::>( + RpcRequest::IsBlockhashValid, + json!([blockhash.to_string(), commitment,]), + ) + .await? + .value + }; + Ok(result) + } + + #[allow(deprecated)] + pub async fn get_fee_for_message(&self, message: &Message) -> ClientResult { + if self.get_node_version().await? < semver::Version::new(1, 9, 0) { + let fee_calculator = self + .get_fee_calculator_for_blockhash(&message.recent_blockhash) + .await? + .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()))?; + Ok(fee_calculator + .lamports_per_signature + .saturating_mul(message.header.num_required_signatures as u64)) + } else { + let serialized_encoded = + serialize_and_encode::(message, UiTransactionEncoding::Base64)?; + let result = self + .send::>>( + RpcRequest::GetFeeForMessage, + json!([serialized_encoded, self.commitment()]), + ) + .await?; + result + .value + .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()).into()) + } + } + + pub async fn get_new_latest_blockhash(&self, blockhash: &Hash) -> ClientResult { + let mut num_retries = 0; + let start = Instant::now(); + while start.elapsed().as_secs() < 5 { + if let Ok(new_blockhash) = self.get_latest_blockhash().await { + if new_blockhash != *blockhash { + return Ok(new_blockhash); + } + } + debug!("Got same blockhash ({:?}), will retry...", blockhash); + + // Retry ~twice during a slot + sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)).await; + num_retries += 1; + } + Err(RpcError::ForUser(format!( + "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", + start.elapsed().as_millis(), + num_retries, + blockhash + )) + .into()) + } + + pub async fn send(&self, request: RpcRequest, params: Value) -> ClientResult + where + T: serde::de::DeserializeOwned, + { + assert!(params.is_array() || params.is_null()); + + let response = self + .sender + .send(request, params) + .await + .map_err(|err| err.into_with_request(request))?; + serde_json::from_value(response) + .map_err(|err| ClientError::new_with_request(err.into(), request)) + } + + pub fn get_transport_stats(&self) -> RpcTransportStats { + self.sender.get_transport_stats() + } +} + +fn serialize_and_encode(input: &T, encoding: UiTransactionEncoding) -> ClientResult +where + T: serde::ser::Serialize, +{ + let serialized = serialize(input) + .map_err(|e| ClientErrorKind::Custom(format!("Serialization failed: {}", e)))?; + let encoded = match encoding { + UiTransactionEncoding::Base58 => bs58::encode(serialized).into_string(), + UiTransactionEncoding::Base64 => base64::encode(serialized), + _ => { + return Err(ClientErrorKind::Custom(format!( + "unsupported encoding: {}. Supported encodings: base58, base64", + encoding + )) + .into()) + } + }; + Ok(encoded) +} + +pub(crate) fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String { + if tls { + format!("https://{}", rpc_addr) + } else { + format!("http://{}", rpc_addr) + } +} + +pub(crate) fn parse_keyed_accounts( + accounts: Vec, + request: RpcRequest, +) -> ClientResult> { + let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::with_capacity(accounts.len()); + for RpcKeyedAccount { pubkey, account } in accounts.into_iter() { + let pubkey = pubkey.parse().map_err(|_| { + ClientError::new_with_request( + RpcError::ParseError("Pubkey".to_string()).into(), + request, + ) + })?; + pubkey_accounts.push(( + pubkey, + account.decode().ok_or_else(|| { + ClientError::new_with_request( + RpcError::ParseError("Account from rpc".to_string()).into(), + request, + ) + })?, + )); + } + Ok(pubkey_accounts) +} diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index 5b447ada8b..475ba1a503 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -7,32 +7,27 @@ //! [JSON-RPC]: https://www.jsonrpc.org/specification #[allow(deprecated)] -use crate::rpc_deprecated_config::{ - RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, - RpcGetConfirmedSignaturesForAddress2Config, -}; +use crate::rpc_deprecated_config::{RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig}; use { crate::{ - client_error::{ClientError, ClientErrorKind, Result as ClientResult}, + client_error::Result as ClientResult, http_sender::HttpSender, mock_sender::{MockSender, Mocks}, + nonblocking::{self, rpc_client::get_rpc_request_str}, rpc_config::{RpcAccountInfoConfig, *}, - rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter}, + rpc_request::{RpcRequest, TokenAccountsFilter}, rpc_response::*, rpc_sender::*, - spinner, }, - bincode::serialize, - log::*, - serde_json::{json, Value}, + serde_json::Value, solana_account_decoder::{ - parse_token::{TokenAccountType, UiTokenAccount, UiTokenAmount}, - UiAccount, UiAccountData, UiAccountEncoding, + parse_token::{UiTokenAccount, UiTokenAmount}, + UiAccount, UiAccountEncoding, }, solana_sdk::{ account::Account, - clock::{Epoch, Slot, UnixTimestamp, DEFAULT_MS_PER_SLOT, MAX_HASH_AGE_IN_SECONDS}, - commitment_config::{CommitmentConfig, CommitmentLevel}, + clock::{Epoch, Slot, UnixTimestamp}, + commitment_config::CommitmentConfig, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, fee_calculator::{FeeCalculator, FeeRateGovernor}, @@ -40,31 +35,23 @@ use { message::Message, pubkey::Pubkey, signature::Signature, - transaction::{self, uses_durable_nonce, Transaction}, + transaction::{self, Transaction}, }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, }, - solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY, - std::{ - cmp::min, - net::SocketAddr, - str::FromStr, - sync::RwLock, - thread::sleep, - time::{Duration, Instant}, - }, + std::{net::SocketAddr, str::FromStr, time::Duration}, }; #[derive(Default)] pub struct RpcClientConfig { - commitment_config: CommitmentConfig, - confirm_transaction_initial_timeout: Option, + pub commitment_config: CommitmentConfig, + pub confirm_transaction_initial_timeout: Option, } impl RpcClientConfig { - fn with_commitment(commitment_config: CommitmentConfig) -> Self { + pub fn with_commitment(commitment_config: CommitmentConfig) -> Self { RpcClientConfig { commitment_config, ..Self::default() @@ -72,6 +59,14 @@ impl RpcClientConfig { } } +#[derive(Debug, Default)] +pub struct GetConfirmedSignaturesForAddress2Config { + pub before: Option, + pub until: Option, + pub limit: Option, + pub commitment: Option, +} + /// A client of a remote Solana node. /// /// `RpcClient` communicates with a Solana node over [JSON-RPC], with the @@ -151,9 +146,14 @@ impl RpcClientConfig { /// returns `true`. The default timeout is 30 seconds, and may be changed by /// calling an appropriate constructor with a `timeout` parameter. pub struct RpcClient { - sender: Box, - config: RpcClientConfig, - node_version: RwLock>, + rpc_client: nonblocking::rpc_client::RpcClient, + runtime: Option, +} + +impl Drop for RpcClient { + fn drop(&mut self) { + self.runtime.take().expect("runtime").shutdown_background(); + } } impl RpcClient { @@ -168,9 +168,15 @@ impl RpcClient { config: RpcClientConfig, ) -> Self { Self { - sender: Box::new(sender), - node_version: RwLock::new(None), - config, + rpc_client: nonblocking::rpc_client::RpcClient::new_sender(sender, config), + runtime: Some( + tokio::runtime::Builder::new_current_thread() + .thread_name("rpc-client") + .enable_io() + .enable_time() + .build() + .unwrap(), + ), } } @@ -455,24 +461,6 @@ impl RpcClient { Self::new_with_timeout(url, timeout) } - fn get_node_version(&self) -> Result { - let r_node_version = self.node_version.read().unwrap(); - if let Some(version) = &*r_node_version { - Ok(version.clone()) - } else { - drop(r_node_version); - let mut w_node_version = self.node_version.write().unwrap(); - let node_version = self.get_version().map_err(|e| { - RpcError::RpcRequestError(format!("cluster version query failed: {}", e)) - })?; - let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| { - RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e)) - })?; - *w_node_version = Some(node_version.clone()); - Ok(node_version) - } - } - /// Get the configured default [commitment level][cl]. /// /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment @@ -487,44 +475,7 @@ impl RpcClient { /// explicitly provide a [`CommitmentConfig`], like /// [`RpcClient::confirm_transaction_with_commitment`]. pub fn commitment(&self) -> CommitmentConfig { - self.config.commitment_config - } - - fn use_deprecated_commitment(&self) -> Result { - Ok(self.get_node_version()? < semver::Version::new(1, 5, 5)) - } - - fn maybe_map_commitment( - &self, - requested_commitment: CommitmentConfig, - ) -> Result { - if matches!( - requested_commitment.commitment, - CommitmentLevel::Finalized | CommitmentLevel::Confirmed | CommitmentLevel::Processed - ) && self.use_deprecated_commitment()? - { - return Ok(CommitmentConfig::use_deprecated_commitment( - requested_commitment, - )); - } - Ok(requested_commitment) - } - - #[allow(deprecated)] - fn maybe_map_request(&self, mut request: RpcRequest) -> Result { - if self.get_node_version()? < semver::Version::new(1, 7, 0) { - request = match request { - RpcRequest::GetBlock => RpcRequest::GetConfirmedBlock, - RpcRequest::GetBlocks => RpcRequest::GetConfirmedBlocks, - RpcRequest::GetBlocksWithLimit => RpcRequest::GetConfirmedBlocksWithLimit, - RpcRequest::GetSignaturesForAddress => { - RpcRequest::GetConfirmedSignaturesForAddress2 - } - RpcRequest::GetTransaction => RpcRequest::GetConfirmedTransaction, - _ => request, - }; - } - Ok(request) + self.rpc_client.commitment() } /// Submit a transaction and wait for confirmation. @@ -591,59 +542,16 @@ impl RpcClient { &self, transaction: &Transaction, ) -> ClientResult { - const SEND_RETRIES: usize = 1; - const GET_STATUS_RETRIES: usize = usize::MAX; - - 'sending: for _ in 0..SEND_RETRIES { - let signature = self.send_transaction(transaction)?; - - let recent_blockhash = if uses_durable_nonce(transaction).is_some() { - let (recent_blockhash, ..) = - self.get_latest_blockhash_with_commitment(CommitmentConfig::processed())?; - recent_blockhash - } else { - transaction.message.recent_blockhash - }; - - for status_retry in 0..GET_STATUS_RETRIES { - match self.get_signature_status(&signature)? { - Some(Ok(_)) => return Ok(signature), - Some(Err(e)) => return Err(e.into()), - None => { - if !self - .is_blockhash_valid(&recent_blockhash, CommitmentConfig::processed())? - { - // Block hash is not found by some reason - break 'sending; - } else if cfg!(not(test)) - // Ignore sleep at last step. - && status_retry < GET_STATUS_RETRIES - { - // Retry twice a second - sleep(Duration::from_millis(500)); - continue; - } - } - } - } - } - - Err(RpcError::ForUser( - "unable to confirm transaction. \ - This can happen in situations such as transaction expiration \ - and insufficient fee-payer funds" - .to_string(), - ) - .into()) + self.invoke(self.rpc_client.send_and_confirm_transaction(transaction)) } pub fn send_and_confirm_transaction_with_spinner( &self, transaction: &Transaction, ) -> ClientResult { - self.send_and_confirm_transaction_with_spinner_and_commitment( - transaction, - self.commitment(), + self.invoke( + self.rpc_client + .send_and_confirm_transaction_with_spinner(transaction), ) } @@ -652,13 +560,9 @@ impl RpcClient { transaction: &Transaction, commitment: CommitmentConfig, ) -> ClientResult { - self.send_and_confirm_transaction_with_spinner_and_config( - transaction, - commitment, - RpcSendTransactionConfig { - preflight_commitment: Some(commitment.commitment), - ..RpcSendTransactionConfig::default() - }, + self.invoke( + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_commitment(transaction, commitment), ) } @@ -668,15 +572,14 @@ impl RpcClient { commitment: CommitmentConfig, config: RpcSendTransactionConfig, ) -> ClientResult { - let recent_blockhash = if uses_durable_nonce(transaction).is_some() { - self.get_latest_blockhash_with_commitment(CommitmentConfig::processed())? - .0 - } else { - transaction.message.recent_blockhash - }; - let signature = self.send_transaction_with_config(transaction, config)?; - self.confirm_transaction_with_spinner(&signature, &recent_blockhash, commitment)?; - Ok(signature) + self.invoke( + self.rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + transaction, + commitment, + config, + ), + ) } /// Submits a signed transaction to the network. @@ -749,15 +652,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn send_transaction(&self, transaction: &Transaction) -> ClientResult { - self.send_transaction_with_config( - transaction, - RpcSendTransactionConfig { - preflight_commitment: Some( - self.maybe_map_commitment(self.commitment())?.commitment, - ), - ..RpcSendTransactionConfig::default() - }, - ) + self.invoke(self.rpc_client.send_transaction(transaction)) } /// Submits a signed transaction to the network. @@ -843,80 +738,17 @@ impl RpcClient { transaction: &Transaction, config: RpcSendTransactionConfig, ) -> ClientResult { - let encoding = if let Some(encoding) = config.encoding { - encoding - } else { - self.default_cluster_transaction_encoding()? - }; - let preflight_commitment = CommitmentConfig { - commitment: config.preflight_commitment.unwrap_or_default(), - }; - let preflight_commitment = self.maybe_map_commitment(preflight_commitment)?; - let config = RpcSendTransactionConfig { - encoding: Some(encoding), - preflight_commitment: Some(preflight_commitment.commitment), - ..config - }; - let serialized_encoded = serialize_and_encode::(transaction, encoding)?; - let signature_base58_str: String = match self.send( - RpcRequest::SendTransaction, - json!([serialized_encoded, config]), - ) { - Ok(signature_base58_str) => signature_base58_str, - Err(err) => { - if let ClientErrorKind::RpcError(RpcError::RpcResponseError { - code, - message, - data, - }) = &err.kind - { - debug!("{} {}", code, message); - if let RpcResponseErrorData::SendTransactionPreflightFailure( - RpcSimulateTransactionResult { - logs: Some(logs), .. - }, - ) = data - { - for (i, log) in logs.iter().enumerate() { - debug!("{:>3}: {}", i + 1, log); - } - debug!(""); - } - } - return Err(err); - } - }; - - let signature = signature_base58_str - .parse::() - .map_err(|err| Into::::into(RpcError::ParseError(err.to_string())))?; - // A mismatching RPC response signature indicates an issue with the RPC node, and - // should not be passed along to confirmation methods. The transaction may or may - // not have been submitted to the cluster, so callers should verify the success of - // the correct transaction signature independently. - if signature != transaction.signatures[0] { - Err(RpcError::RpcRequestError(format!( - "RPC node returned mismatched signature {:?}, expected {:?}", - signature, transaction.signatures[0] - )) - .into()) - } else { - Ok(transaction.signatures[0]) - } + self.invoke( + self.rpc_client + .send_transaction_with_config(transaction, config), + ) } pub fn send(&self, request: RpcRequest, params: Value) -> ClientResult where T: serde::de::DeserializeOwned, { - assert!(params.is_array() || params.is_null()); - - let response = self - .sender - .send(request, params) - .map_err(|err| err.into_with_request(request))?; - serde_json::from_value(response) - .map_err(|err| ClientError::new_with_request(err.into(), request)) + self.invoke(self.rpc_client.send(request, params)) } /// Check the confirmation status of a transaction. @@ -972,9 +804,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult { - Ok(self - .confirm_transaction_with_commitment(signature, self.commitment())? - .value) + self.invoke(self.rpc_client.confirm_transaction(signature)) } /// Check the confirmation status of a transaction. @@ -1036,113 +866,23 @@ impl RpcClient { signature: &Signature, commitment_config: CommitmentConfig, ) -> RpcResult { - let Response { context, value } = self.get_signature_statuses(&[*signature])?; - - Ok(Response { - context, - value: value[0] - .as_ref() - .filter(|result| result.satisfies_commitment(commitment_config)) - .map(|result| result.status.is_ok()) - .unwrap_or_default(), - }) + self.invoke( + self.rpc_client + .confirm_transaction_with_commitment(signature, commitment_config), + ) } pub fn confirm_transaction_with_spinner( &self, signature: &Signature, recent_blockhash: &Hash, - commitment: CommitmentConfig, + commitment_config: CommitmentConfig, ) -> ClientResult<()> { - let desired_confirmations = if commitment.is_finalized() { - MAX_LOCKOUT_HISTORY + 1 - } else { - 1 - }; - let mut confirmations = 0; - - let progress_bar = spinner::new_progress_bar(); - - progress_bar.set_message(format!( - "[{}/{}] Finalizing transaction {}", - confirmations, desired_confirmations, signature, - )); - - let now = Instant::now(); - let confirm_transaction_initial_timeout = self - .config - .confirm_transaction_initial_timeout - .unwrap_or_default(); - let (signature, status) = loop { - // Get recent commitment in order to count confirmations for successful transactions - let status = self - .get_signature_status_with_commitment(signature, CommitmentConfig::processed())?; - if status.is_none() { - let blockhash_not_found = - !self.is_blockhash_valid(recent_blockhash, CommitmentConfig::processed())?; - if blockhash_not_found && now.elapsed() >= confirm_transaction_initial_timeout { - break (signature, status); - } - } else { - break (signature, status); - } - - if cfg!(not(test)) { - sleep(Duration::from_millis(500)); - } - }; - if let Some(result) = status { - if let Err(err) = result { - return Err(err.into()); - } - } else { - return Err(RpcError::ForUser( - "unable to confirm transaction. \ - This can happen in situations such as transaction expiration \ - and insufficient fee-payer funds" - .to_string(), - ) - .into()); - } - let now = Instant::now(); - loop { - // Return when specified commitment is reached - // Failed transactions have already been eliminated, `is_some` check is sufficient - if self - .get_signature_status_with_commitment(signature, commitment)? - .is_some() - { - progress_bar.set_message("Transaction confirmed"); - progress_bar.finish_and_clear(); - return Ok(()); - } - - progress_bar.set_message(format!( - "[{}/{}] Finalizing transaction {}", - min(confirmations + 1, desired_confirmations), - desired_confirmations, - signature, - )); - sleep(Duration::from_millis(500)); - confirmations = self - .get_num_blocks_since_signature_confirmation(signature) - .unwrap_or(confirmations); - if now.elapsed().as_secs() >= MAX_HASH_AGE_IN_SECONDS as u64 { - return Err( - RpcError::ForUser("transaction not finalized. \ - This can happen when a transaction lands in an abandoned fork. \ - Please retry.".to_string()).into(), - ); - } - } - } - - fn default_cluster_transaction_encoding(&self) -> Result { - if self.get_node_version()? < semver::Version::new(1, 3, 16) { - Ok(UiTransactionEncoding::Base58) - } else { - Ok(UiTransactionEncoding::Base64) - } + self.invoke(self.rpc_client.confirm_transaction_with_spinner( + signature, + recent_blockhash, + commitment_config, + )) } /// Simulates sending a transaction. @@ -1203,13 +943,7 @@ impl RpcClient { &self, transaction: &Transaction, ) -> RpcResult { - self.simulate_transaction_with_config( - transaction, - RpcSimulateTransactionConfig { - commitment: Some(self.commitment()), - ..RpcSimulateTransactionConfig::default() - }, - ) + self.invoke(self.rpc_client.simulate_transaction(transaction)) } /// Simulates sending a transaction. @@ -1287,22 +1021,9 @@ impl RpcClient { transaction: &Transaction, config: RpcSimulateTransactionConfig, ) -> RpcResult { - let encoding = if let Some(encoding) = config.encoding { - encoding - } else { - self.default_cluster_transaction_encoding()? - }; - let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment)?; - let config = RpcSimulateTransactionConfig { - encoding: Some(encoding), - commitment: Some(commitment), - ..config - }; - let serialized_encoded = serialize_and_encode::(transaction, encoding)?; - self.send( - RpcRequest::SimulateTransaction, - json!([serialized_encoded, config]), + self.invoke( + self.rpc_client + .simulate_transaction_with_config(transaction, config), ) } @@ -1329,15 +1050,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_highest_snapshot_slot(&self) -> ClientResult { - if self.get_node_version()? < semver::Version::new(1, 9, 0) { - #[allow(deprecated)] - self.get_snapshot_slot().map(|full| RpcSnapshotSlotInfo { - full, - incremental: None, - }) - } else { - self.send(RpcRequest::GetHighestSnapshotSlot, Value::Null) - } + self.invoke(self.rpc_client.get_highest_snapshot_slot()) } #[deprecated( @@ -1346,7 +1059,7 @@ impl RpcClient { )] #[allow(deprecated)] pub fn get_snapshot_slot(&self) -> ClientResult { - self.send(RpcRequest::GetSnapshotSlot, Value::Null) + self.invoke(self.rpc_client.get_snapshot_slot()) } /// Check if a transaction has been processed with the default [commitment level][cl]. @@ -1407,7 +1120,7 @@ impl RpcClient { &self, signature: &Signature, ) -> ClientResult>> { - self.get_signature_status_with_commitment(signature, self.commitment()) + self.invoke(self.rpc_client.get_signature_status(signature)) } /// Gets the statuses of a list of transaction signatures. @@ -1485,8 +1198,7 @@ impl RpcClient { &self, signatures: &[Signature], ) -> RpcResult>> { - let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect(); - self.send(RpcRequest::GetSignatureStatuses, json!([signatures])) + self.invoke(self.rpc_client.get_signature_statuses(signatures)) } /// Gets the statuses of a list of transaction signatures. @@ -1554,12 +1266,9 @@ impl RpcClient { &self, signatures: &[Signature], ) -> RpcResult>> { - let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect(); - self.send( - RpcRequest::GetSignatureStatuses, - json!([signatures, { - "searchTransactionHistory": true - }]), + self.invoke( + self.rpc_client + .get_signature_statuses_with_history(signatures), ) } @@ -1626,14 +1335,10 @@ impl RpcClient { signature: &Signature, commitment_config: CommitmentConfig, ) -> ClientResult>> { - let result: Response>> = self.send( - RpcRequest::GetSignatureStatuses, - json!([[signature.to_string()]]), - )?; - Ok(result.value[0] - .clone() - .filter(|result| result.satisfies_commitment(commitment_config)) - .map(|status_meta| status_meta.status)) + self.invoke( + self.rpc_client + .get_signature_status_with_commitment(signature, commitment_config), + ) } /// Check if a transaction has been processed with the given [commitment level][cl]. @@ -1698,16 +1403,14 @@ impl RpcClient { commitment_config: CommitmentConfig, search_transaction_history: bool, ) -> ClientResult>> { - let result: Response>> = self.send( - RpcRequest::GetSignatureStatuses, - json!([[signature.to_string()], { - "searchTransactionHistory": search_transaction_history - }]), - )?; - Ok(result.value[0] - .clone() - .filter(|result| result.satisfies_commitment(commitment_config)) - .map(|status_meta| status_meta.status)) + self.invoke( + self.rpc_client + .get_signature_status_with_commitment_and_history( + signature, + commitment_config, + search_transaction_history, + ), + ) } /// Returns the slot that has reached the configured [commitment level][cl]. @@ -1732,7 +1435,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_slot(&self) -> ClientResult { - self.get_slot_with_commitment(self.commitment()) + self.invoke(self.rpc_client.get_slot()) } /// Returns the slot that has reached the given [commitment level][cl]. @@ -1762,10 +1465,7 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetSlot, - json!([self.maybe_map_commitment(commitment_config)?]), - ) + self.invoke(self.rpc_client.get_slot_with_commitment(commitment_config)) } /// Returns the block height that has reached the configured [commitment level][cl]. @@ -1790,7 +1490,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_block_height(&self) -> ClientResult { - self.get_block_height_with_commitment(self.commitment()) + self.invoke(self.rpc_client.get_block_height()) } /// Returns the block height that has reached the given [commitment level][cl]. @@ -1822,9 +1522,9 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetBlockHeight, - json!([self.maybe_map_commitment(commitment_config)?]), + self.invoke( + self.rpc_client + .get_block_height_with_commitment(commitment_config), ) } @@ -1851,21 +1551,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_slot_leaders(&self, start_slot: Slot, limit: u64) -> ClientResult> { - self.send(RpcRequest::GetSlotLeaders, json!([start_slot, limit])) - .and_then(|slot_leaders: Vec| { - slot_leaders - .iter() - .map(|slot_leader| { - Pubkey::from_str(slot_leader).map_err(|err| { - ClientErrorKind::Custom(format!( - "pubkey deserialization failed: {}", - err - )) - .into() - }) - }) - .collect() - }) + self.invoke(self.rpc_client.get_slot_leaders(start_slot, limit)) } /// Get block production for the current epoch. @@ -1888,7 +1574,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_block_production(&self) -> RpcResult { - self.send(RpcRequest::GetBlockProduction, Value::Null) + self.invoke(self.rpc_client.get_block_production()) } /// Get block production for the current or previous epoch. @@ -1936,7 +1622,7 @@ impl RpcClient { &self, config: RpcBlockProductionConfig, ) -> RpcResult { - self.send(RpcRequest::GetBlockProduction, json!([config])) + self.invoke(self.rpc_client.get_block_production_with_config(config)) } /// Returns epoch activation information for a stake account. @@ -2015,16 +1701,7 @@ impl RpcClient { stake_account: Pubkey, epoch: Option, ) -> ClientResult { - self.send( - RpcRequest::GetStakeActivation, - json!([ - stake_account.to_string(), - RpcEpochConfig { - epoch, - commitment: Some(self.commitment()), - } - ]), - ) + self.invoke(self.rpc_client.get_stake_activation(stake_account, epoch)) } /// Returns information about the current supply. @@ -2051,7 +1728,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn supply(&self) -> RpcResult { - self.supply_with_commitment(self.commitment()) + self.invoke(self.rpc_client.supply()) } /// Returns information about the current supply. @@ -2081,10 +1758,7 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> RpcResult { - self.send( - RpcRequest::GetSupply, - json!([self.maybe_map_commitment(commitment_config)?]), - ) + self.invoke(self.rpc_client.supply_with_commitment(commitment_config)) } /// Returns the 20 largest accounts, by lamport balance. @@ -2121,13 +1795,7 @@ impl RpcClient { &self, config: RpcLargestAccountsConfig, ) -> RpcResult> { - let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment)?; - let config = RpcLargestAccountsConfig { - commitment: Some(commitment), - ..config - }; - self.send(RpcRequest::GetLargestAccounts, json!([config])) + self.invoke(self.rpc_client.get_largest_accounts_with_config(config)) } /// Returns the account info and associated stake for all the voting accounts @@ -2154,7 +1822,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_vote_accounts(&self) -> ClientResult { - self.get_vote_accounts_with_commitment(self.commitment()) + self.invoke(self.rpc_client.get_vote_accounts()) } /// Returns the account info and associated stake for all the voting accounts @@ -2187,10 +1855,10 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.get_vote_accounts_with_config(RpcGetVoteAccountsConfig { - commitment: Some(self.maybe_map_commitment(commitment_config)?), - ..RpcGetVoteAccountsConfig::default() - }) + self.invoke( + self.rpc_client + .get_vote_accounts_with_commitment(commitment_config), + ) } /// Returns the account info and associated stake for all the voting accounts @@ -2236,7 +1904,7 @@ impl RpcClient { &self, config: RpcGetVoteAccountsConfig, ) -> ClientResult { - self.send(RpcRequest::GetVoteAccounts, json!([config])) + self.invoke(self.rpc_client.get_vote_accounts_with_config(config)) } pub fn wait_for_max_stake( @@ -2244,31 +1912,10 @@ impl RpcClient { commitment: CommitmentConfig, max_stake_percent: f32, ) -> ClientResult<()> { - let mut current_percent; - loop { - let vote_accounts = self.get_vote_accounts_with_commitment(commitment)?; - - let mut max = 0; - let total_active_stake = vote_accounts - .current - .iter() - .chain(vote_accounts.delinquent.iter()) - .map(|vote_account| { - max = std::cmp::max(max, vote_account.activated_stake); - vote_account.activated_stake - }) - .sum::(); - current_percent = 100f32 * max as f32 / total_active_stake as f32; - if current_percent < max_stake_percent { - break; - } - info!( - "Waiting for stake to drop below {} current: {:.1}", - max_stake_percent, current_percent - ); - sleep(Duration::from_secs(10)); - } - Ok(()) + self.invoke( + self.rpc_client + .wait_for_max_stake(commitment, max_stake_percent), + ) } /// Returns information about all the nodes participating in the cluster. @@ -2292,7 +1939,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_cluster_nodes(&self) -> ClientResult> { - self.send(RpcRequest::GetClusterNodes, Value::Null) + self.invoke(self.rpc_client.get_cluster_nodes()) } /// Returns identity and transaction information about a confirmed block in the ledger. @@ -2324,7 +1971,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_block(&self, slot: Slot) -> ClientResult { - self.get_block_with_encoding(slot, UiTransactionEncoding::Json) + self.invoke(self.rpc_client.get_block(slot)) } /// Returns identity and transaction information about a confirmed block in the ledger. @@ -2357,10 +2004,7 @@ impl RpcClient { slot: Slot, encoding: UiTransactionEncoding, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetBlock)?, - json!([slot, encoding]), - ) + self.invoke(self.rpc_client.get_block_with_encoding(slot, encoding)) } /// Returns identity and transaction information about a confirmed block in the ledger. @@ -2402,16 +2046,13 @@ impl RpcClient { slot: Slot, config: RpcBlockConfig, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetBlock)?, - json!([slot, config]), - ) + self.invoke(self.rpc_client.get_block_with_config(slot, config)) } #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_block() instead")] #[allow(deprecated)] pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult { - self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json) + self.invoke(self.rpc_client.get_confirmed_block(slot)) } #[deprecated( @@ -2424,7 +2065,10 @@ impl RpcClient { slot: Slot, encoding: UiTransactionEncoding, ) -> ClientResult { - self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding])) + self.invoke( + self.rpc_client + .get_confirmed_block_with_encoding(slot, encoding), + ) } #[deprecated( @@ -2437,7 +2081,10 @@ impl RpcClient { slot: Slot, config: RpcConfirmedBlockConfig, ) -> ClientResult { - self.send(RpcRequest::GetConfirmedBlock, json!([slot, config])) + self.invoke( + self.rpc_client + .get_confirmed_block_with_config(slot, config), + ) } /// Returns a list of finalized blocks between two slots. @@ -2486,10 +2133,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_blocks(&self, start_slot: Slot, end_slot: Option) -> ClientResult> { - self.send( - self.maybe_map_request(RpcRequest::GetBlocks)?, - json!([start_slot, end_slot]), - ) + self.invoke(self.rpc_client.get_blocks(start_slot, end_slot)) } /// Returns a list of confirmed blocks between two slots. @@ -2554,16 +2198,11 @@ impl RpcClient { end_slot: Option, commitment_config: CommitmentConfig, ) -> ClientResult> { - let json = if end_slot.is_some() { - json!([ - start_slot, - end_slot, - self.maybe_map_commitment(commitment_config)? - ]) - } else { - json!([start_slot, self.maybe_map_commitment(commitment_config)?]) - }; - self.send(self.maybe_map_request(RpcRequest::GetBlocks)?, json) + self.invoke(self.rpc_client.get_blocks_with_commitment( + start_slot, + end_slot, + commitment_config, + )) } /// Returns a list of finalized blocks starting at the given slot. @@ -2601,10 +2240,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_blocks_with_limit(&self, start_slot: Slot, limit: usize) -> ClientResult> { - self.send( - self.maybe_map_request(RpcRequest::GetBlocksWithLimit)?, - json!([start_slot, limit]), - ) + self.invoke(self.rpc_client.get_blocks_with_limit(start_slot, limit)) } /// Returns a list of confirmed blocks starting at the given slot. @@ -2654,14 +2290,11 @@ impl RpcClient { limit: usize, commitment_config: CommitmentConfig, ) -> ClientResult> { - self.send( - self.maybe_map_request(RpcRequest::GetBlocksWithLimit)?, - json!([ - start_slot, - limit, - self.maybe_map_commitment(commitment_config)? - ]), - ) + self.invoke(self.rpc_client.get_blocks_with_limit_and_commitment( + start_slot, + limit, + commitment_config, + )) } #[deprecated(since = "1.7.0", note = "Please use RpcClient::get_blocks() instead")] @@ -2671,10 +2304,7 @@ impl RpcClient { start_slot: Slot, end_slot: Option, ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocks, - json!([start_slot, end_slot]), - ) + self.invoke(self.rpc_client.get_confirmed_blocks(start_slot, end_slot)) } #[deprecated( @@ -2688,16 +2318,11 @@ impl RpcClient { end_slot: Option, commitment_config: CommitmentConfig, ) -> ClientResult> { - let json = if end_slot.is_some() { - json!([ - start_slot, - end_slot, - self.maybe_map_commitment(commitment_config)? - ]) - } else { - json!([start_slot, self.maybe_map_commitment(commitment_config)?]) - }; - self.send(RpcRequest::GetConfirmedBlocks, json) + self.invoke(self.rpc_client.get_confirmed_blocks_with_commitment( + start_slot, + end_slot, + commitment_config, + )) } #[deprecated( @@ -2710,9 +2335,9 @@ impl RpcClient { start_slot: Slot, limit: usize, ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocksWithLimit, - json!([start_slot, limit]), + self.invoke( + self.rpc_client + .get_confirmed_blocks_with_limit(start_slot, limit), ) } @@ -2727,13 +2352,13 @@ impl RpcClient { limit: usize, commitment_config: CommitmentConfig, ) -> ClientResult> { - self.send( - RpcRequest::GetConfirmedBlocksWithLimit, - json!([ - start_slot, - limit, - self.maybe_map_commitment(commitment_config)? - ]), + self.invoke( + self.rpc_client + .get_confirmed_blocks_with_limit_and_commitment( + start_slot, + limit, + commitment_config, + ), ) } @@ -2778,10 +2403,7 @@ impl RpcClient { &self, address: &Pubkey, ) -> ClientResult> { - self.get_signatures_for_address_with_config( - address, - GetConfirmedSignaturesForAddress2Config::default(), - ) + self.invoke(self.rpc_client.get_signatures_for_address(address)) } /// Get confirmed signatures for transactions involving an address. @@ -2841,19 +2463,10 @@ impl RpcClient { address: &Pubkey, config: GetConfirmedSignaturesForAddress2Config, ) -> ClientResult> { - let config = RpcSignaturesForAddressConfig { - before: config.before.map(|signature| signature.to_string()), - until: config.until.map(|signature| signature.to_string()), - limit: config.limit, - commitment: config.commitment, - }; - - let result: Vec = self.send( - self.maybe_map_request(RpcRequest::GetSignaturesForAddress)?, - json!([address.to_string(), config]), - )?; - - Ok(result) + self.invoke( + self.rpc_client + .get_signatures_for_address_with_config(address, config), + ) } #[deprecated( @@ -2865,9 +2478,9 @@ impl RpcClient { &self, address: &Pubkey, ) -> ClientResult> { - self.get_confirmed_signatures_for_address2_with_config( - address, - GetConfirmedSignaturesForAddress2Config::default(), + self.invoke( + self.rpc_client + .get_confirmed_signatures_for_address2(address), ) } @@ -2881,19 +2494,10 @@ impl RpcClient { address: &Pubkey, config: GetConfirmedSignaturesForAddress2Config, ) -> ClientResult> { - let config = RpcGetConfirmedSignaturesForAddress2Config { - before: config.before.map(|signature| signature.to_string()), - until: config.until.map(|signature| signature.to_string()), - limit: config.limit, - commitment: config.commitment, - }; - - let result: Vec = self.send( - RpcRequest::GetConfirmedSignaturesForAddress2, - json!([address.to_string(), config]), - )?; - - Ok(result) + self.invoke( + self.rpc_client + .get_confirmed_signatures_for_address2_with_config(address, config), + ) } /// Returns transaction details for a confirmed transaction. @@ -2944,10 +2548,7 @@ impl RpcClient { signature: &Signature, encoding: UiTransactionEncoding, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetTransaction)?, - json!([signature.to_string(), encoding]), - ) + self.invoke(self.rpc_client.get_transaction(signature, encoding)) } /// Returns transaction details for a confirmed transaction. @@ -3007,9 +2608,9 @@ impl RpcClient { signature: &Signature, config: RpcTransactionConfig, ) -> ClientResult { - self.send( - self.maybe_map_request(RpcRequest::GetTransaction)?, - json!([signature.to_string(), config]), + self.invoke( + self.rpc_client + .get_transaction_with_config(signature, config), ) } @@ -3023,9 +2624,9 @@ impl RpcClient { signature: &Signature, encoding: UiTransactionEncoding, ) -> ClientResult { - self.send( - RpcRequest::GetConfirmedTransaction, - json!([signature.to_string(), encoding]), + self.invoke( + self.rpc_client + .get_confirmed_transaction(signature, encoding), ) } @@ -3039,9 +2640,9 @@ impl RpcClient { signature: &Signature, config: RpcConfirmedTransactionConfig, ) -> ClientResult { - self.send( - RpcRequest::GetConfirmedTransaction, - json!([signature.to_string(), config]), + self.invoke( + self.rpc_client + .get_confirmed_transaction_with_config(signature, config), ) } @@ -3067,20 +2668,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_block_time(&self, slot: Slot) -> ClientResult { - let request = RpcRequest::GetBlockTime; - let response = self.sender.send(request, json!([slot])); - - response - .map(|result_json| { - if result_json.is_null() { - return Err(RpcError::ForUser(format!("Block Not Found: slot={}", slot)).into()); - } - let result = serde_json::from_value(result_json) - .map_err(|err| ClientError::new_with_request(err.into(), request))?; - trace!("Response block timestamp {:?} {:?}", slot, result); - Ok(result) - }) - .map_err(|err| err.into_with_request(request))? + self.invoke(self.rpc_client.get_block_time(slot)) } /// Returns information about the current epoch. @@ -3107,7 +2695,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_epoch_info(&self) -> ClientResult { - self.get_epoch_info_with_commitment(self.commitment()) + self.invoke(self.rpc_client.get_epoch_info()) } /// Returns information about the current epoch. @@ -3137,9 +2725,9 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetEpochInfo, - json!([self.maybe_map_commitment(commitment_config)?]), + self.invoke( + self.rpc_client + .get_epoch_info_with_commitment(commitment_config), ) } @@ -3174,7 +2762,7 @@ impl RpcClient { &self, slot: Option, ) -> ClientResult> { - self.get_leader_schedule_with_commitment(slot, self.commitment()) + self.invoke(self.rpc_client.get_leader_schedule(slot)) } /// Returns the leader schedule for an epoch. @@ -3207,12 +2795,9 @@ impl RpcClient { slot: Option, commitment_config: CommitmentConfig, ) -> ClientResult> { - self.get_leader_schedule_with_config( - slot, - RpcLeaderScheduleConfig { - commitment: Some(self.maybe_map_commitment(commitment_config)?), - ..RpcLeaderScheduleConfig::default() - }, + self.invoke( + self.rpc_client + .get_leader_schedule_with_commitment(slot, commitment_config), ) } @@ -3251,7 +2836,10 @@ impl RpcClient { slot: Option, config: RpcLeaderScheduleConfig, ) -> ClientResult> { - self.send(RpcRequest::GetLeaderSchedule, json!([slot, config])) + self.invoke( + self.rpc_client + .get_leader_schedule_with_config(slot, config), + ) } /// Returns epoch schedule information from this cluster's genesis config. @@ -3274,7 +2862,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_epoch_schedule(&self) -> ClientResult { - self.send(RpcRequest::GetEpochSchedule, Value::Null) + self.invoke(self.rpc_client.get_epoch_schedule()) } /// Returns a list of recent performance samples, in reverse slot order. @@ -3306,7 +2894,7 @@ impl RpcClient { &self, limit: Option, ) -> ClientResult> { - self.send(RpcRequest::GetRecentPerformanceSamples, json!([limit])) + self.invoke(self.rpc_client.get_recent_performance_samples(limit)) } /// Returns the identity pubkey for the current node. @@ -3329,14 +2917,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_identity(&self) -> ClientResult { - let rpc_identity: RpcIdentity = self.send(RpcRequest::GetIdentity, Value::Null)?; - - rpc_identity.identity.parse::().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Pubkey".to_string()).into(), - RpcRequest::GetIdentity, - ) - }) + self.invoke(self.rpc_client.get_identity()) } /// Returns the current inflation governor. @@ -3365,7 +2946,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_inflation_governor(&self) -> ClientResult { - self.send(RpcRequest::GetInflationGovernor, Value::Null) + self.invoke(self.rpc_client.get_inflation_governor()) } /// Returns the specific inflation values for the current epoch. @@ -3388,7 +2969,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_inflation_rate(&self) -> ClientResult { - self.send(RpcRequest::GetInflationRate, Value::Null) + self.invoke(self.rpc_client.get_inflation_rate()) } /// Returns the inflation reward for a list of addresses for an epoch. @@ -3428,20 +3009,7 @@ impl RpcClient { addresses: &[Pubkey], epoch: Option, ) -> ClientResult>> { - let addresses: Vec<_> = addresses - .iter() - .map(|address| address.to_string()) - .collect(); - self.send( - RpcRequest::GetInflationReward, - json!([ - addresses, - RpcEpochConfig { - epoch, - commitment: Some(self.commitment()), - } - ]), - ) + self.invoke(self.rpc_client.get_inflation_reward(addresses, epoch)) } /// Returns the current solana version running on the node. @@ -3468,7 +3036,7 @@ impl RpcClient { /// # Ok::<(), Box>(()) /// ``` pub fn get_version(&self) -> ClientResult { - self.send(RpcRequest::GetVersion, Value::Null) + self.invoke(self.rpc_client.get_version()) } /// Returns the lowest slot that the node has information about in its ledger. @@ -3495,7 +3063,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn minimum_ledger_slot(&self) -> ClientResult { - self.send(RpcRequest::MinimumLedgerSlot, Value::Null) + self.invoke(self.rpc_client.minimum_ledger_slot()) } /// Returns all information associated with the account of the provided pubkey. @@ -3542,9 +3110,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_account(&self, pubkey: &Pubkey) -> ClientResult { - self.get_account_with_commitment(pubkey, self.commitment())? - .value - .ok_or_else(|| RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into()) + self.invoke(self.rpc_client.get_account(pubkey)) } /// Returns all information associated with the account of the provided pubkey. @@ -3591,41 +3157,10 @@ impl RpcClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> RpcResult> { - let config = RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - commitment: Some(self.maybe_map_commitment(commitment_config)?), - data_slice: None, - }; - let response = self.sender.send( - RpcRequest::GetAccountInfo, - json!([pubkey.to_string(), config]), - ); - - response - .map(|result_json| { - if result_json.is_null() { - return Err( - RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(), - ); - } - let Response { - context, - value: rpc_account, - } = serde_json::from_value::>>(result_json)?; - trace!("Response account {:?} {:?}", pubkey, rpc_account); - let account = rpc_account.and_then(|rpc_account| rpc_account.decode()); - - Ok(Response { - context, - value: account, - }) - }) - .map_err(|err| { - Into::::into(RpcError::ForUser(format!( - "AccountNotFound: pubkey={}: {}", - pubkey, err - ))) - })? + self.invoke( + self.rpc_client + .get_account_with_commitment(pubkey, commitment_config), + ) } /// Get the max slot seen from retransmit stage. @@ -3648,7 +3183,7 @@ impl RpcClient { /// let slot = rpc_client.get_max_retransmit_slot()?; /// # Ok::<(), ClientError>(()) pub fn get_max_retransmit_slot(&self) -> ClientResult { - self.send(RpcRequest::GetMaxRetransmitSlot, Value::Null) + self.invoke(self.rpc_client.get_max_retransmit_slot()) } /// Get the max slot seen from after [shred](https://docs.solana.com/terminology#shred) insert. @@ -3671,7 +3206,7 @@ impl RpcClient { /// let slot = rpc_client.get_max_shred_insert_slot()?; /// # Ok::<(), ClientError>(()) pub fn get_max_shred_insert_slot(&self) -> ClientResult { - self.send(RpcRequest::GetMaxShredInsertSlot, Value::Null) + self.invoke(self.rpc_client.get_max_shred_insert_slot()) } /// Returns the account information for a list of pubkeys. @@ -3705,9 +3240,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_multiple_accounts(&self, pubkeys: &[Pubkey]) -> ClientResult>> { - Ok(self - .get_multiple_accounts_with_commitment(pubkeys, self.commitment())? - .value) + self.invoke(self.rpc_client.get_multiple_accounts(pubkeys)) } /// Returns the account information for a list of pubkeys. @@ -3746,13 +3279,9 @@ impl RpcClient { pubkeys: &[Pubkey], commitment_config: CommitmentConfig, ) -> RpcResult>> { - self.get_multiple_accounts_with_config( - pubkeys, - RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - commitment: Some(self.maybe_map_commitment(commitment_config)?), - data_slice: None, - }, + self.invoke( + self.rpc_client + .get_multiple_accounts_with_commitment(pubkeys, commitment_config), ) } @@ -3799,24 +3328,10 @@ impl RpcClient { pubkeys: &[Pubkey], config: RpcAccountInfoConfig, ) -> RpcResult>> { - let config = RpcAccountInfoConfig { - commitment: config.commitment.or_else(|| Some(self.commitment())), - ..config - }; - let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect(); - let response = self.send(RpcRequest::GetMultipleAccounts, json!([pubkeys, config]))?; - let Response { - context, - value: accounts, - } = serde_json::from_value::>>>(response)?; - let accounts: Vec> = accounts - .into_iter() - .map(|rpc_account| rpc_account.and_then(|a| a.decode())) - .collect(); - Ok(Response { - context, - value: accounts, - }) + self.invoke( + self.rpc_client + .get_multiple_accounts_with_config(pubkeys, config), + ) } /// Gets the raw data associated with an account. @@ -3853,7 +3368,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_account_data(&self, pubkey: &Pubkey) -> ClientResult> { - Ok(self.get_account(pubkey)?.data) + self.invoke(self.rpc_client.get_account_data(pubkey)) } /// Returns minimum balance required to make an account with specified data length rent exempt. @@ -3878,20 +3393,10 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> ClientResult { - let request = RpcRequest::GetMinimumBalanceForRentExemption; - let minimum_balance_json = self - .sender - .send(request, json!([data_len])) - .map_err(|err| err.into_with_request(request))?; - - let minimum_balance: u64 = serde_json::from_value(minimum_balance_json) - .map_err(|err| ClientError::new_with_request(err.into(), request))?; - trace!( - "Response minimum balance {:?} {:?}", - data_len, - minimum_balance - ); - Ok(minimum_balance) + self.invoke( + self.rpc_client + .get_minimum_balance_for_rent_exemption(data_len), + ) } /// Request the balance of the provided account pubkey. @@ -3923,9 +3428,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_balance(&self, pubkey: &Pubkey) -> ClientResult { - Ok(self - .get_balance_with_commitment(pubkey, self.commitment())? - .value) + self.invoke(self.rpc_client.get_balance(pubkey)) } /// Request the balance of the provided account pubkey. @@ -3962,12 +3465,9 @@ impl RpcClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> RpcResult { - self.send( - RpcRequest::GetBalance, - json!([ - pubkey.to_string(), - self.maybe_map_commitment(commitment_config)? - ]), + self.invoke( + self.rpc_client + .get_balance_with_commitment(pubkey, commitment_config), ) } @@ -4001,16 +3501,7 @@ impl RpcClient { /// # Ok::<(), ClientError>(()) /// ``` pub fn get_program_accounts(&self, pubkey: &Pubkey) -> ClientResult> { - self.get_program_accounts_with_config( - pubkey, - RpcProgramAccountsConfig { - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - ..RpcAccountInfoConfig::default() - }, - ..RpcProgramAccountsConfig::default() - }, - ) + self.invoke(self.rpc_client.get_program_accounts(pubkey)) } /// Returns all accounts owned by the provided program pubkey. @@ -4073,38 +3564,24 @@ impl RpcClient { pubkey: &Pubkey, config: RpcProgramAccountsConfig, ) -> ClientResult> { - let commitment = config - .account_config - .commitment - .unwrap_or_else(|| self.commitment()); - let commitment = self.maybe_map_commitment(commitment)?; - let account_config = RpcAccountInfoConfig { - commitment: Some(commitment), - ..config.account_config - }; - let config = RpcProgramAccountsConfig { - account_config, - ..config - }; - let accounts: Vec = self.send( - RpcRequest::GetProgramAccounts, - json!([pubkey.to_string(), config]), - )?; - parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts) + self.invoke( + self.rpc_client + .get_program_accounts_with_config(pubkey, config), + ) } /// Request the transaction count. pub fn get_transaction_count(&self) -> ClientResult { - self.get_transaction_count_with_commitment(self.commitment()) + self.invoke(self.rpc_client.get_transaction_count()) } pub fn get_transaction_count_with_commitment( &self, commitment_config: CommitmentConfig, ) -> ClientResult { - self.send( - RpcRequest::GetTransactionCount, - json!([self.maybe_map_commitment(commitment_config)?]), + self.invoke( + self.rpc_client + .get_transaction_count_with_commitment(commitment_config), ) } @@ -4114,8 +3591,7 @@ impl RpcClient { )] #[allow(deprecated)] pub fn get_fees(&self) -> ClientResult { - #[allow(deprecated)] - Ok(self.get_fees_with_commitment(self.commitment())?.value) + self.invoke(self.rpc_client.get_fees()) } #[deprecated( @@ -4124,37 +3600,13 @@ impl RpcClient { )] #[allow(deprecated)] pub fn get_fees_with_commitment(&self, commitment_config: CommitmentConfig) -> RpcResult { - let Response { - context, - value: fees, - } = self.send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config)?]), - )?; - let blockhash = fees.blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetFees, - ) - })?; - Ok(Response { - context, - value: Fees { - blockhash, - fee_calculator: fees.fee_calculator, - last_valid_block_height: fees.last_valid_block_height, - }, - }) + self.invoke(self.rpc_client.get_fees_with_commitment(commitment_config)) } #[deprecated(since = "1.9.0", note = "Please use `get_latest_blockhash` instead")] #[allow(deprecated)] pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> { - #[allow(deprecated)] - let (blockhash, fee_calculator, _last_valid_slot) = self - .get_recent_blockhash_with_commitment(self.commitment())? - .value; - Ok((blockhash, fee_calculator)) + self.invoke(self.rpc_client.get_recent_blockhash()) } #[deprecated( @@ -4166,63 +3618,10 @@ impl RpcClient { &self, commitment_config: CommitmentConfig, ) -> RpcResult<(Hash, FeeCalculator, Slot)> { - let (context, blockhash, fee_calculator, last_valid_slot) = if let Ok(Response { - context, - value: - RpcFees { - blockhash, - fee_calculator, - last_valid_slot, - .. - }, - }) = self - .send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config)?]), - ) { - (context, blockhash, fee_calculator, last_valid_slot) - } else if let Ok(Response { - context, - value: - DeprecatedRpcFees { - blockhash, - fee_calculator, - last_valid_slot, - }, - }) = self.send::>( - RpcRequest::GetFees, - json!([self.maybe_map_commitment(commitment_config)?]), - ) { - (context, blockhash, fee_calculator, last_valid_slot) - } else if let Ok(Response { - context, - value: - RpcBlockhashFeeCalculator { - blockhash, - fee_calculator, - }, - }) = self.send::>( - RpcRequest::GetRecentBlockhash, - json!([self.maybe_map_commitment(commitment_config)?]), - ) { - (context, blockhash, fee_calculator, 0) - } else { - return Err(ClientError::new_with_request( - RpcError::ParseError("RpcBlockhashFeeCalculator or RpcFees".to_string()).into(), - RpcRequest::GetRecentBlockhash, - )); - }; - - let blockhash = blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetRecentBlockhash, - ) - })?; - Ok(Response { - context, - value: (blockhash, fee_calculator, last_valid_slot), - }) + self.invoke( + self.rpc_client + .get_recent_blockhash_with_commitment(commitment_config), + ) } #[deprecated(since = "1.9.0", note = "Please `get_fee_for_message` instead")] @@ -4231,10 +3630,7 @@ impl RpcClient { &self, blockhash: &Hash, ) -> ClientResult> { - #[allow(deprecated)] - Ok(self - .get_fee_calculator_for_blockhash_with_commitment(blockhash, self.commitment())? - .value) + self.invoke(self.rpc_client.get_fee_calculator_for_blockhash(blockhash)) } #[deprecated( @@ -4247,18 +3643,10 @@ impl RpcClient { blockhash: &Hash, commitment_config: CommitmentConfig, ) -> RpcResult> { - let Response { context, value } = self.send::>>( - RpcRequest::GetFeeCalculatorForBlockhash, - json!([ - blockhash.to_string(), - self.maybe_map_commitment(commitment_config)? - ]), - )?; - - Ok(Response { - context, - value: value.map(|rf| rf.fee_calculator), - }) + self.invoke( + self.rpc_client + .get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment_config), + ) } #[deprecated( @@ -4267,16 +3655,7 @@ impl RpcClient { )] #[allow(deprecated)] pub fn get_fee_rate_governor(&self) -> RpcResult { - let Response { - context, - value: RpcFeeRateGovernor { fee_rate_governor }, - } = - self.send::>(RpcRequest::GetFeeRateGovernor, Value::Null)?; - - Ok(Response { - context, - value: fee_rate_governor, - }) + self.invoke(self.rpc_client.get_fee_rate_governor()) } #[deprecated( @@ -4285,54 +3664,23 @@ impl RpcClient { )] #[allow(deprecated)] pub fn get_new_blockhash(&self, blockhash: &Hash) -> ClientResult<(Hash, FeeCalculator)> { - let mut num_retries = 0; - let start = Instant::now(); - while start.elapsed().as_secs() < 5 { - #[allow(deprecated)] - if let Ok((new_blockhash, fee_calculator)) = self.get_recent_blockhash() { - if new_blockhash != *blockhash { - return Ok((new_blockhash, fee_calculator)); - } - } - debug!("Got same blockhash ({:?}), will retry...", blockhash); - - // Retry ~twice during a slot - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)); - num_retries += 1; - } - Err(RpcError::ForUser(format!( - "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", - start.elapsed().as_millis(), - num_retries, - blockhash - )) - .into()) + self.invoke(self.rpc_client.get_new_blockhash(blockhash)) } pub fn get_first_available_block(&self) -> ClientResult { - self.send(RpcRequest::GetFirstAvailableBlock, Value::Null) + self.invoke(self.rpc_client.get_first_available_block()) } pub fn get_genesis_hash(&self) -> ClientResult { - let hash_str: String = self.send(RpcRequest::GetGenesisHash, Value::Null)?; - let hash = hash_str.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetGenesisHash, - ) - })?; - Ok(hash) + self.invoke(self.rpc_client.get_genesis_hash()) } pub fn get_health(&self) -> ClientResult<()> { - self.send::(RpcRequest::GetHealth, Value::Null) - .map(|_| ()) + self.invoke(self.rpc_client.get_health()) } pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult> { - Ok(self - .get_token_account_with_commitment(pubkey, self.commitment())? - .value) + self.invoke(self.rpc_client.get_token_account(pubkey)) } pub fn get_token_account_with_commitment( @@ -4340,60 +3688,14 @@ impl RpcClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> RpcResult> { - let config = RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config)?), - data_slice: None, - }; - let response = self.sender.send( - RpcRequest::GetAccountInfo, - json!([pubkey.to_string(), config]), - ); - - response - .map(|result_json| { - if result_json.is_null() { - return Err( - RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(), - ); - } - let Response { - context, - value: rpc_account, - } = serde_json::from_value::>>(result_json)?; - trace!("Response account {:?} {:?}", pubkey, rpc_account); - let response = { - if let Some(rpc_account) = rpc_account { - if let UiAccountData::Json(account_data) = rpc_account.data { - let token_account_type: TokenAccountType = - serde_json::from_value(account_data.parsed)?; - if let TokenAccountType::Account(token_account) = token_account_type { - return Ok(Response { - context, - value: Some(token_account), - }); - } - } - } - Err(Into::::into(RpcError::ForUser(format!( - "Account could not be parsed as token account: pubkey={}", - pubkey - )))) - }; - response? - }) - .map_err(|err| { - Into::::into(RpcError::ForUser(format!( - "AccountNotFound: pubkey={}: {}", - pubkey, err - ))) - })? + self.invoke( + self.rpc_client + .get_token_account_with_commitment(pubkey, commitment_config), + ) } pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult { - Ok(self - .get_token_account_balance_with_commitment(pubkey, self.commitment())? - .value) + self.invoke(self.rpc_client.get_token_account_balance(pubkey)) } pub fn get_token_account_balance_with_commitment( @@ -4401,12 +3703,9 @@ impl RpcClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> RpcResult { - self.send( - RpcRequest::GetTokenAccountBalance, - json!([ - pubkey.to_string(), - self.maybe_map_commitment(commitment_config)? - ]), + self.invoke( + self.rpc_client + .get_token_account_balance_with_commitment(pubkey, commitment_config), ) } @@ -4415,13 +3714,10 @@ impl RpcClient { delegate: &Pubkey, token_account_filter: TokenAccountsFilter, ) -> ClientResult> { - Ok(self - .get_token_accounts_by_delegate_with_commitment( - delegate, - token_account_filter, - self.commitment(), - )? - .value) + self.invoke( + self.rpc_client + .get_token_accounts_by_delegate(delegate, token_account_filter), + ) } pub fn get_token_accounts_by_delegate_with_commitment( @@ -4430,22 +3726,13 @@ impl RpcClient { token_account_filter: TokenAccountsFilter, commitment_config: CommitmentConfig, ) -> RpcResult> { - let token_account_filter = match token_account_filter { - TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()), - TokenAccountsFilter::ProgramId(program_id) => { - RpcTokenAccountsFilter::ProgramId(program_id.to_string()) - } - }; - - let config = RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config)?), - data_slice: None, - }; - - self.send( - RpcRequest::GetTokenAccountsByOwner, - json!([delegate.to_string(), token_account_filter, config]), + self.invoke( + self.rpc_client + .get_token_accounts_by_delegate_with_commitment( + delegate, + token_account_filter, + commitment_config, + ), ) } @@ -4454,13 +3741,10 @@ impl RpcClient { owner: &Pubkey, token_account_filter: TokenAccountsFilter, ) -> ClientResult> { - Ok(self - .get_token_accounts_by_owner_with_commitment( - owner, - token_account_filter, - self.commitment(), - )? - .value) + self.invoke( + self.rpc_client + .get_token_accounts_by_owner(owner, token_account_filter), + ) } pub fn get_token_accounts_by_owner_with_commitment( @@ -4469,29 +3753,15 @@ impl RpcClient { token_account_filter: TokenAccountsFilter, commitment_config: CommitmentConfig, ) -> RpcResult> { - let token_account_filter = match token_account_filter { - TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()), - TokenAccountsFilter::ProgramId(program_id) => { - RpcTokenAccountsFilter::ProgramId(program_id.to_string()) - } - }; - - let config = RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::JsonParsed), - commitment: Some(self.maybe_map_commitment(commitment_config)?), - data_slice: None, - }; - - self.send( - RpcRequest::GetTokenAccountsByOwner, - json!([owner.to_string(), token_account_filter, config]), - ) + self.invoke(self.rpc_client.get_token_accounts_by_owner_with_commitment( + owner, + token_account_filter, + commitment_config, + )) } pub fn get_token_supply(&self, mint: &Pubkey) -> ClientResult { - Ok(self - .get_token_supply_with_commitment(mint, self.commitment())? - .value) + self.invoke(self.rpc_client.get_token_supply(mint)) } pub fn get_token_supply_with_commitment( @@ -4499,24 +3769,14 @@ impl RpcClient { mint: &Pubkey, commitment_config: CommitmentConfig, ) -> RpcResult { - self.send( - RpcRequest::GetTokenSupply, - json!([ - mint.to_string(), - self.maybe_map_commitment(commitment_config)? - ]), + self.invoke( + self.rpc_client + .get_token_supply_with_commitment(mint, commitment_config), ) } pub fn request_airdrop(&self, pubkey: &Pubkey, lamports: u64) -> ClientResult { - self.request_airdrop_with_config( - pubkey, - lamports, - RpcRequestAirdropConfig { - commitment: Some(self.commitment()), - ..RpcRequestAirdropConfig::default() - }, - ) + self.invoke(self.rpc_client.request_airdrop(pubkey, lamports)) } pub fn request_airdrop_with_blockhash( @@ -4525,14 +3785,11 @@ impl RpcClient { lamports: u64, recent_blockhash: &Hash, ) -> ClientResult { - self.request_airdrop_with_config( + self.invoke(self.rpc_client.request_airdrop_with_blockhash( pubkey, lamports, - RpcRequestAirdropConfig { - commitment: Some(self.commitment()), - recent_blockhash: Some(recent_blockhash.to_string()), - }, - ) + recent_blockhash, + )) } pub fn request_airdrop_with_config( @@ -4541,52 +3798,10 @@ impl RpcClient { lamports: u64, config: RpcRequestAirdropConfig, ) -> ClientResult { - let commitment = config.commitment.unwrap_or_default(); - let commitment = self.maybe_map_commitment(commitment)?; - let config = RpcRequestAirdropConfig { - commitment: Some(commitment), - ..config - }; - self.send( - RpcRequest::RequestAirdrop, - json!([pubkey.to_string(), lamports, config]), + self.invoke( + self.rpc_client + .request_airdrop_with_config(pubkey, lamports, config), ) - .and_then(|signature: String| { - Signature::from_str(&signature).map_err(|err| { - ClientErrorKind::Custom(format!("signature deserialization failed: {}", err)).into() - }) - }) - .map_err(|_| { - RpcError::ForUser( - "airdrop request failed. \ - This can happen when the rate limit is reached." - .to_string(), - ) - .into() - }) - } - - fn poll_balance_with_timeout_and_commitment( - &self, - pubkey: &Pubkey, - polling_frequency: &Duration, - timeout: &Duration, - commitment_config: CommitmentConfig, - ) -> ClientResult { - let now = Instant::now(); - loop { - match self.get_balance_with_commitment(pubkey, commitment_config) { - Ok(bal) => { - return Ok(bal.value); - } - Err(e) => { - sleep(*polling_frequency); - if now.elapsed() > *timeout { - return Err(e); - } - } - }; - } } pub fn poll_get_balance_with_commitment( @@ -4594,11 +3809,9 @@ impl RpcClient { pubkey: &Pubkey, commitment_config: CommitmentConfig, ) -> ClientResult { - self.poll_balance_with_timeout_and_commitment( - pubkey, - &Duration::from_millis(100), - &Duration::from_secs(1), - commitment_config, + self.invoke( + self.rpc_client + .poll_get_balance_with_commitment(pubkey, commitment_config), ) } @@ -4608,31 +3821,17 @@ impl RpcClient { expected_balance: Option, commitment_config: CommitmentConfig, ) -> Option { - const LAST: usize = 30; - for run in 0..LAST { - let balance_result = self.poll_get_balance_with_commitment(pubkey, commitment_config); - if expected_balance.is_none() { - return balance_result.ok(); - } - trace!( - "wait_for_balance_with_commitment [{}] {:?} {:?}", - run, - balance_result, - expected_balance - ); - if let (Some(expected_balance), Ok(balance_result)) = (expected_balance, balance_result) - { - if expected_balance == balance_result { - return Some(balance_result); - } - } - } - None + self.invoke(self.rpc_client.wait_for_balance_with_commitment( + pubkey, + expected_balance, + commitment_config, + )) + .ok() } /// Poll the server to confirm a transaction. pub fn poll_for_signature(&self, signature: &Signature) -> ClientResult<()> { - self.poll_for_signature_with_commitment(signature, self.commitment()) + self.invoke(self.rpc_client.poll_for_signature(signature)) } /// Poll the server to confirm a transaction. @@ -4641,23 +3840,10 @@ impl RpcClient { signature: &Signature, commitment_config: CommitmentConfig, ) -> ClientResult<()> { - let now = Instant::now(); - loop { - if let Ok(Some(_)) = - self.get_signature_status_with_commitment(signature, commitment_config) - { - break; - } - if now.elapsed().as_secs() > 15 { - return Err(RpcError::ForUser(format!( - "signature not found after {} seconds", - now.elapsed().as_secs() - )) - .into()); - } - sleep(Duration::from_millis(250)); - } - Ok(()) + self.invoke( + self.rpc_client + .poll_for_signature_with_commitment(signature, commitment_config), + ) } /// Poll the server to confirm a transaction. @@ -4666,79 +3852,24 @@ impl RpcClient { signature: &Signature, min_confirmed_blocks: usize, ) -> ClientResult { - let mut now = Instant::now(); - let mut confirmed_blocks = 0; - loop { - let response = self.get_num_blocks_since_signature_confirmation(signature); - match response { - Ok(count) => { - if confirmed_blocks != count { - info!( - "signature {} confirmed {} out of {} after {} ms", - signature, - count, - min_confirmed_blocks, - now.elapsed().as_millis() - ); - now = Instant::now(); - confirmed_blocks = count; - } - if count >= min_confirmed_blocks { - break; - } - } - Err(err) => { - debug!("check_confirmations request failed: {:?}", err); - } - }; - if now.elapsed().as_secs() > 20 { - info!( - "signature {} confirmed {} out of {} failed after {} ms", - signature, - confirmed_blocks, - min_confirmed_blocks, - now.elapsed().as_millis() - ); - if confirmed_blocks > 0 { - return Ok(confirmed_blocks); - } else { - return Err(RpcError::ForUser(format!( - "signature not found after {} seconds", - now.elapsed().as_secs() - )) - .into()); - } - } - sleep(Duration::from_millis(250)); - } - Ok(confirmed_blocks) + self.invoke( + self.rpc_client + .poll_for_signature_confirmation(signature, min_confirmed_blocks), + ) } pub fn get_num_blocks_since_signature_confirmation( &self, signature: &Signature, ) -> ClientResult { - let result: Response>> = self.send( - RpcRequest::GetSignatureStatuses, - json!([[signature.to_string()]]), - )?; - - let confirmations = result.value[0] - .clone() - .ok_or_else(|| { - ClientError::new_with_request( - ClientErrorKind::Custom("signature not found".to_string()), - RpcRequest::GetSignatureStatuses, - ) - })? - .confirmations - .unwrap_or(MAX_LOCKOUT_HISTORY + 1); - Ok(confirmations) + self.invoke( + self.rpc_client + .get_num_blocks_since_signature_confirmation(signature), + ) } pub fn get_latest_blockhash(&self) -> ClientResult { - let (blockhash, _) = self.get_latest_blockhash_with_commitment(self.commitment())?; - Ok(blockhash) + self.invoke(self.rpc_client.get_latest_blockhash()) } #[allow(deprecated)] @@ -4746,33 +3877,10 @@ impl RpcClient { &self, commitment: CommitmentConfig, ) -> ClientResult<(Hash, u64)> { - let (blockhash, last_valid_block_height) = - if self.get_node_version()? < semver::Version::new(1, 9, 0) { - let Fees { - blockhash, - last_valid_block_height, - .. - } = self.get_fees_with_commitment(commitment)?.value; - (blockhash, last_valid_block_height) - } else { - let RpcBlockhash { - blockhash, - last_valid_block_height, - } = self - .send::>( - RpcRequest::GetLatestBlockhash, - json!([self.maybe_map_commitment(commitment)?]), - )? - .value; - let blockhash = blockhash.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Hash".to_string()).into(), - RpcRequest::GetLatestBlockhash, - ) - })?; - (blockhash, last_valid_block_height) - }; - Ok((blockhash, last_valid_block_height)) + self.invoke( + self.rpc_client + .get_latest_blockhash_with_commitment(commitment), + ) } #[allow(deprecated)] @@ -4781,132 +3889,30 @@ impl RpcClient { blockhash: &Hash, commitment: CommitmentConfig, ) -> ClientResult { - let result = if self.get_node_version()? < semver::Version::new(1, 9, 0) { - self.get_fee_calculator_for_blockhash_with_commitment(blockhash, commitment)? - .value - .is_some() - } else { - self.send::>( - RpcRequest::IsBlockhashValid, - json!([blockhash.to_string(), commitment,]), - )? - .value - }; - Ok(result) + self.invoke(self.rpc_client.is_blockhash_valid(blockhash, commitment)) } #[allow(deprecated)] pub fn get_fee_for_message(&self, message: &Message) -> ClientResult { - if self.get_node_version()? < semver::Version::new(1, 9, 0) { - let fee_calculator = self - .get_fee_calculator_for_blockhash(&message.recent_blockhash)? - .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()))?; - Ok(fee_calculator - .lamports_per_signature - .saturating_mul(message.header.num_required_signatures as u64)) - } else { - let serialized_encoded = - serialize_and_encode::(message, UiTransactionEncoding::Base64)?; - let result = self.send::>>( - RpcRequest::GetFeeForMessage, - json!([serialized_encoded, self.commitment()]), - )?; - result - .value - .ok_or_else(|| ClientErrorKind::Custom("Invalid blockhash".to_string()).into()) - } + self.invoke(self.rpc_client.get_fee_for_message(message)) } pub fn get_new_latest_blockhash(&self, blockhash: &Hash) -> ClientResult { - let mut num_retries = 0; - let start = Instant::now(); - while start.elapsed().as_secs() < 5 { - if let Ok(new_blockhash) = self.get_latest_blockhash() { - if new_blockhash != *blockhash { - return Ok(new_blockhash); - } - } - debug!("Got same blockhash ({:?}), will retry...", blockhash); - - // Retry ~twice during a slot - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT / 2)); - num_retries += 1; - } - Err(RpcError::ForUser(format!( - "Unable to get new blockhash after {}ms (retried {} times), stuck at {}", - start.elapsed().as_millis(), - num_retries, - blockhash - )) - .into()) + self.invoke(self.rpc_client.get_new_latest_blockhash(blockhash)) } pub fn get_transport_stats(&self) -> RpcTransportStats { - self.sender.get_transport_stats() + self.rpc_client.get_transport_stats() } -} -pub fn serialize_and_encode(input: &T, encoding: UiTransactionEncoding) -> ClientResult -where - T: serde::ser::Serialize, -{ - let serialized = serialize(input) - .map_err(|e| ClientErrorKind::Custom(format!("Serialization failed: {}", e)))?; - let encoded = match encoding { - UiTransactionEncoding::Base58 => bs58::encode(serialized).into_string(), - UiTransactionEncoding::Base64 => base64::encode(serialized), - _ => { - return Err(ClientErrorKind::Custom(format!( - "unsupported encoding: {}. Supported encodings: base58, base64", - encoding - )) - .into()) - } - }; - Ok(encoded) -} - -#[derive(Debug, Default)] -pub struct GetConfirmedSignaturesForAddress2Config { - pub before: Option, - pub until: Option, - pub limit: Option, - pub commitment: Option, -} - -fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String { - if tls { - format!("https://{}", rpc_addr) - } else { - format!("http://{}", rpc_addr) + fn invoke>>(&self, f: F) -> ClientResult { + // `block_on()` panics if called within an asynchronous execution context. Whereas + // `block_in_place()` only panics if called from a current_thread runtime, which is the + // lesser evil. + tokio::task::block_in_place(move || self.runtime.as_ref().expect("runtime").block_on(f)) } } -fn parse_keyed_accounts( - accounts: Vec, - request: RpcRequest, -) -> ClientResult> { - let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::with_capacity(accounts.len()); - for RpcKeyedAccount { pubkey, account } in accounts.into_iter() { - let pubkey = pubkey.parse().map_err(|_| { - ClientError::new_with_request( - RpcError::ParseError("Pubkey".to_string()).into(), - request, - ) - })?; - pubkey_accounts.push(( - pubkey, - account.decode().ok_or_else(|| { - ClientError::new_with_request( - RpcError::ParseError("Account from rpc".to_string()).into(), - request, - ) - })?, - )); - } - Ok(pubkey_accounts) -} - /// Mocks for documentation examples #[doc(hidden)] pub fn create_rpc_client_mocks() -> crate::mock_sender::Mocks { @@ -4943,7 +3949,7 @@ mod tests { crossbeam_channel::unbounded, jsonrpc_core::{futures::prelude::*, Error, IoHandler, Params}, jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}, - serde_json::Number, + serde_json::{json, Number}, solana_sdk::{ instruction::InstructionError, signature::{Keypair, Signer}, @@ -4960,7 +3966,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] #[should_panic(expected = "can call blocking only when running on the multi-threaded runtime")] - async fn test_send_async_current_thread_should_panic() { + async fn test_send_async_current_thread() { _test_send(); } diff --git a/client/src/rpc_sender.rs b/client/src/rpc_sender.rs index 8bf45af522..c71f17d730 100644 --- a/client/src/rpc_sender.rs +++ b/client/src/rpc_sender.rs @@ -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; +#[async_trait] +pub(crate) trait RpcSender { + async fn send( + &self, + request: RpcRequest, + params: serde_json::Value, + ) -> Result; fn get_transport_stats(&self) -> RpcTransportStats; } diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 8e2ed64ace..8ccf7fe285 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -3142,6 +3142,7 @@ dependencies = [ name = "solana-client" version = "1.10.0" dependencies = [ + "async-trait", "base64 0.13.0", "bincode", "bs58 0.4.0", diff --git a/test-validator/Cargo.toml b/test-validator/Cargo.toml index 1c70a937d8..e9611cb6dc 100644 --- a/test-validator/Cargo.toml +++ b/test-validator/Cargo.toml @@ -27,6 +27,7 @@ solana-rpc = { path = "../rpc", version = "=1.10.0" } solana-runtime = { path = "../runtime", version = "=1.10.0" } solana-sdk = { path = "../sdk", version = "=1.10.0" } solana-streamer = { path = "../streamer", version = "=1.10.0" } +tokio = { version = "1", features = ["full"] } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 983dd5c942..06018fc664 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -2,7 +2,7 @@ use { log::*, solana_cli_output::CliAccount, - solana_client::rpc_client::RpcClient, + solana_client::{nonblocking, rpc_client::RpcClient}, solana_core::{ tower_storage::TowerStorage, validator::{Validator, ValidatorConfig, ValidatorStartProgress}, @@ -43,9 +43,9 @@ use { path::{Path, PathBuf}, str::FromStr, sync::{Arc, RwLock}, - thread::sleep, time::Duration, }, + tokio::time::sleep, }; #[derive(Clone)] @@ -358,9 +358,39 @@ impl TestValidatorGenesis { socket_addr_space: SocketAddrSpace, ) -> (TestValidator, Keypair) { let mint_keypair = Keypair::new(); - TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space) - .map(|test_validator| (test_validator, mint_keypair)) - .expect("Test validator failed to start") + match TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space) { + Ok(test_validator) => { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .unwrap(); + runtime.block_on(test_validator.wait_for_nonzero_fees()); + (test_validator, mint_keypair) + } + Err(err) => panic!("Test validator failed to start: {}", err), + } + } + + pub async fn start_async(&self) -> (TestValidator, Keypair) { + self.start_async_with_socket_addr_space(SocketAddrSpace::new( + /*allow_private_addr=*/ true, + )) + .await + } + + pub async fn start_async_with_socket_addr_space( + &self, + socket_addr_space: SocketAddrSpace, + ) -> (TestValidator, Keypair) { + let mint_keypair = Keypair::new(); + match TestValidator::start(mint_keypair.pubkey(), self, socket_addr_space) { + Ok(test_validator) => { + test_validator.wait_for_nonzero_fees().await; + (test_validator, mint_keypair) + } + Err(err) => panic!("Test validator failed to start: {}", err), + } } } @@ -617,53 +647,7 @@ impl TestValidator { discover_cluster(&gossip, 1, socket_addr_space) .map_err(|err| format!("TestValidator startup failed: {:?}", err))?; - // This is a hack to delay until the fees are non-zero for test consistency - // (fees from genesis are zero until the first block with a transaction in it is completed - // due to a bug in the Bank) - { - let rpc_client = - RpcClient::new_with_commitment(rpc_url.clone(), CommitmentConfig::processed()); - let mut message = Message::new( - &[Instruction::new_with_bytes( - Pubkey::new_unique(), - &[], - vec![AccountMeta::new(Pubkey::new_unique(), true)], - )], - None, - ); - const MAX_TRIES: u64 = 10; - let mut num_tries = 0; - loop { - num_tries += 1; - if num_tries > MAX_TRIES { - break; - } - println!("Waiting for fees to stabilize {:?}...", num_tries); - match rpc_client.get_latest_blockhash() { - Ok(blockhash) => { - message.recent_blockhash = blockhash; - match rpc_client.get_fee_for_message(&message) { - Ok(fee) => { - if fee != 0 { - break; - } - } - Err(err) => { - warn!("get_fee_for_message() failed: {:?}", err); - break; - } - } - } - Err(err) => { - warn!("get_latest_blockhash() failed: {:?}", err); - break; - } - } - sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)); - } - } - - Ok(TestValidator { + let test_validator = TestValidator { ledger_path, preserve_ledger, rpc_pubsub_url, @@ -672,7 +656,56 @@ impl TestValidator { gossip, validator, vote_account_address, - }) + }; + Ok(test_validator) + } + + /// This is a hack to delay until the fees are non-zero for test consistency + /// (fees from genesis are zero until the first block with a transaction in it is completed + /// due to a bug in the Bank) + async fn wait_for_nonzero_fees(&self) { + let rpc_client = nonblocking::rpc_client::RpcClient::new_with_commitment( + self.rpc_url.clone(), + CommitmentConfig::processed(), + ); + let mut message = Message::new( + &[Instruction::new_with_bytes( + Pubkey::new_unique(), + &[], + vec![AccountMeta::new(Pubkey::new_unique(), true)], + )], + None, + ); + const MAX_TRIES: u64 = 10; + let mut num_tries = 0; + loop { + num_tries += 1; + if num_tries > MAX_TRIES { + break; + } + println!("Waiting for fees to stabilize {:?}...", num_tries); + match rpc_client.get_latest_blockhash().await { + Ok(blockhash) => { + message.recent_blockhash = blockhash; + match rpc_client.get_fee_for_message(&message).await { + Ok(fee) => { + if fee != 0 { + break; + } + } + Err(err) => { + warn!("get_fee_for_message() failed: {:?}", err); + break; + } + } + } + Err(err) => { + warn!("get_latest_blockhash() failed: {:?}", err); + break; + } + } + sleep(Duration::from_millis(DEFAULT_MS_PER_SLOT)).await; + } } /// Return the validator's TPU address @@ -719,6 +752,14 @@ impl TestValidator { RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::processed()) } + /// Return a nonblocking RpcClient for the validator. + pub fn get_async_rpc_client(&self) -> nonblocking::rpc_client::RpcClient { + nonblocking::rpc_client::RpcClient::new_with_commitment( + self.rpc_url.clone(), + CommitmentConfig::processed(), + ) + } + pub fn join(mut self) { if let Some(validator) = self.validator.take() { validator.join(); @@ -746,3 +787,29 @@ impl Drop for TestValidator { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn get_health() { + let (test_validator, _payer) = TestValidatorGenesis::default().start(); + let rpc_client = test_validator.get_rpc_client(); + rpc_client.get_health().expect("health"); + } + + #[tokio::test] + async fn nonblocking_get_health() { + let (test_validator, _payer) = TestValidatorGenesis::default().start_async().await; + let rpc_client = test_validator.get_async_rpc_client(); + rpc_client.get_health().await.expect("health"); + } + + #[tokio::test] + #[should_panic] + async fn document_tokio_panic() { + // `start()` blows up when run within tokio + let (_test_validator, _payer) = TestValidatorGenesis::default().start(); + } +}