Skip to main content

solana_rpc_client_types/
request.rs

1pub use solana_address::Address;
2use {
3    crate::response::RpcSimulateTransactionResult,
4    serde::{Deserialize, Serialize},
5    serde_json::{json, Value},
6    solana_clock::Slot,
7    std::fmt,
8    thiserror::Error,
9};
10
11#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
12pub enum RpcRequest {
13    Custom { method: &'static str },
14    DeregisterNode,
15    GetAccountInfo,
16    GetBalance,
17    GetBlock,
18    GetBlockHeight,
19    GetBlockProduction,
20    GetBlocks,
21    GetBlocksWithLimit,
22    GetBlockTime,
23    GetClusterNodes,
24    GetEpochInfo,
25    GetEpochSchedule,
26    GetFeeForMessage,
27    GetFirstAvailableBlock,
28    GetGenesisHash,
29    GetHealth,
30    GetIdentity,
31    GetInflationGovernor,
32    GetInflationRate,
33    GetInflationReward,
34    GetLargestAccounts,
35    GetLatestBlockhash,
36    GetLeaderSchedule,
37    GetMaxRetransmitSlot,
38    GetMaxShredInsertSlot,
39    GetMinimumBalanceForRentExemption,
40    GetMultipleAccounts,
41    GetProgramAccounts,
42    GetRecentPerformanceSamples,
43    GetRecentPrioritizationFees,
44    GetHighestSnapshotSlot,
45    GetSignaturesForAddress,
46    GetSignatureStatuses,
47    GetSlot,
48    GetSlotLeader,
49    GetSlotLeaders,
50    GetStorageTurn,
51    GetStorageTurnRate,
52    GetSlotsPerSegment,
53    GetStakeMinimumDelegation,
54    GetStoragePubkeysForSlot,
55    GetSupply,
56    GetTokenAccountBalance,
57    GetTokenAccountsByDelegate,
58    GetTokenAccountsByOwner,
59    GetTokenLargestAccounts,
60    GetTokenSupply,
61    GetTransaction,
62    GetTransactionCount,
63    GetVersion,
64    GetVoteAccounts,
65    IsBlockhashValid,
66    MinimumLedgerSlot,
67    RegisterNode,
68    RequestAirdrop,
69    SendTransaction,
70    SimulateTransaction,
71    SignVote,
72}
73
74#[allow(deprecated)]
75impl fmt::Display for RpcRequest {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        let method = match self {
78            RpcRequest::Custom { method } => method,
79            RpcRequest::DeregisterNode => "deregisterNode",
80            RpcRequest::GetAccountInfo => "getAccountInfo",
81            RpcRequest::GetBalance => "getBalance",
82            RpcRequest::GetBlock => "getBlock",
83            RpcRequest::GetBlockHeight => "getBlockHeight",
84            RpcRequest::GetBlockProduction => "getBlockProduction",
85            RpcRequest::GetBlocks => "getBlocks",
86            RpcRequest::GetBlocksWithLimit => "getBlocksWithLimit",
87            RpcRequest::GetBlockTime => "getBlockTime",
88            RpcRequest::GetClusterNodes => "getClusterNodes",
89            RpcRequest::GetEpochInfo => "getEpochInfo",
90            RpcRequest::GetEpochSchedule => "getEpochSchedule",
91            RpcRequest::GetFeeForMessage => "getFeeForMessage",
92            RpcRequest::GetFirstAvailableBlock => "getFirstAvailableBlock",
93            RpcRequest::GetGenesisHash => "getGenesisHash",
94            RpcRequest::GetHealth => "getHealth",
95            RpcRequest::GetIdentity => "getIdentity",
96            RpcRequest::GetInflationGovernor => "getInflationGovernor",
97            RpcRequest::GetInflationRate => "getInflationRate",
98            RpcRequest::GetInflationReward => "getInflationReward",
99            RpcRequest::GetLargestAccounts => "getLargestAccounts",
100            RpcRequest::GetLatestBlockhash => "getLatestBlockhash",
101            RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
102            RpcRequest::GetMaxRetransmitSlot => "getMaxRetransmitSlot",
103            RpcRequest::GetMaxShredInsertSlot => "getMaxShredInsertSlot",
104            RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
105            RpcRequest::GetMultipleAccounts => "getMultipleAccounts",
106            RpcRequest::GetProgramAccounts => "getProgramAccounts",
107            RpcRequest::GetRecentPerformanceSamples => "getRecentPerformanceSamples",
108            RpcRequest::GetRecentPrioritizationFees => "getRecentPrioritizationFees",
109            RpcRequest::GetHighestSnapshotSlot => "getHighestSnapshotSlot",
110            RpcRequest::GetSignaturesForAddress => "getSignaturesForAddress",
111            RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
112            RpcRequest::GetSlot => "getSlot",
113            RpcRequest::GetSlotLeader => "getSlotLeader",
114            RpcRequest::GetSlotLeaders => "getSlotLeaders",
115            RpcRequest::GetStakeMinimumDelegation => "getStakeMinimumDelegation",
116            RpcRequest::GetStorageTurn => "getStorageTurn",
117            RpcRequest::GetStorageTurnRate => "getStorageTurnRate",
118            RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
119            RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
120            RpcRequest::GetSupply => "getSupply",
121            RpcRequest::GetTokenAccountBalance => "getTokenAccountBalance",
122            RpcRequest::GetTokenAccountsByDelegate => "getTokenAccountsByDelegate",
123            RpcRequest::GetTokenAccountsByOwner => "getTokenAccountsByOwner",
124            RpcRequest::GetTokenSupply => "getTokenSupply",
125            RpcRequest::GetTokenLargestAccounts => "getTokenLargestAccounts",
126            RpcRequest::GetTransaction => "getTransaction",
127            RpcRequest::GetTransactionCount => "getTransactionCount",
128            RpcRequest::GetVersion => "getVersion",
129            RpcRequest::GetVoteAccounts => "getVoteAccounts",
130            RpcRequest::IsBlockhashValid => "isBlockhashValid",
131            RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
132            RpcRequest::RegisterNode => "registerNode",
133            RpcRequest::RequestAirdrop => "requestAirdrop",
134            RpcRequest::SendTransaction => "sendTransaction",
135            RpcRequest::SimulateTransaction => "simulateTransaction",
136            RpcRequest::SignVote => "signVote",
137        };
138
139        write!(f, "{method}")
140    }
141}
142
143// Changing any of these? Update the JSON RPC docs!
144pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
145pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
146pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
147pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000;
148pub const MAX_MULTIPLE_ACCOUNTS: usize = 100;
149pub const NUM_LARGEST_ACCOUNTS: usize = 20;
150pub const MAX_GET_PROGRAM_ACCOUNT_FILTERS: usize = 4;
151pub const MAX_GET_SLOT_LEADERS: usize = 5000;
152
153// Limit the length of the `epoch_credits` array for each validator in a `get_vote_accounts`
154// response
155pub const MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY: usize = 5;
156
157// Validators that are this number of slots behind are considered delinquent
158pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128;
159
160impl RpcRequest {
161    pub fn build_request_json(self, id: u64, params: Value) -> Value {
162        let jsonrpc = "2.0";
163        json!({
164           "jsonrpc": jsonrpc,
165           "id": id,
166           "method": format!("{self}"),
167           "params": params,
168        })
169    }
170}
171
172#[derive(Debug)]
173#[allow(clippy::large_enum_variant)]
174pub enum RpcResponseErrorData {
175    Empty,
176    SendTransactionPreflightFailure(RpcSimulateTransactionResult),
177    NodeUnhealthy { num_slots_behind: Option<Slot> },
178}
179
180impl fmt::Display for RpcResponseErrorData {
181    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182        match self {
183            RpcResponseErrorData::SendTransactionPreflightFailure(
184                RpcSimulateTransactionResult {
185                    logs: Some(logs), ..
186                },
187            ) => {
188                if logs.is_empty() {
189                    Ok(())
190                } else {
191                    writeln!(f, "{} log messages:", logs.len())?;
192                    for log in logs {
193                        writeln!(f, "  {log}")?;
194                    }
195                    Ok(())
196                }
197            }
198            _ => Ok(()),
199        }
200    }
201}
202
203#[derive(Debug, Error)]
204#[allow(clippy::large_enum_variant)]
205pub enum RpcError {
206    #[error("RPC request error: {0}")]
207    RpcRequestError(String),
208    #[error("RPC response error {code}: {message}; {data}")]
209    RpcResponseError {
210        code: i64,
211        message: String,
212        data: RpcResponseErrorData,
213    },
214    #[error("parse error: expected {0}")]
215    ParseError(String), /* "expected" */
216    // Anything in a `ForUser` needs to die.  The caller should be
217    // deciding what to tell their user
218    #[error("{0}")]
219    ForUser(String), /* "direct-to-user message" */
220}
221
222#[derive(Serialize, Deserialize)]
223pub enum TokenAccountsFilter {
224    Mint(Address),
225    ProgramId(Address),
226}
227
228#[cfg(test)]
229mod tests {
230    use {
231        super::*,
232        crate::config::RpcTokenAccountsFilter,
233        solana_commitment_config::{CommitmentConfig, CommitmentLevel},
234    };
235
236    #[test]
237    fn test_build_request_json() {
238        let test_request = RpcRequest::GetAccountInfo;
239        let addr = json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx");
240        let request = test_request.build_request_json(1, json!([addr]));
241        assert_eq!(request["method"], "getAccountInfo");
242        assert_eq!(request["params"], json!([addr]));
243
244        let test_request = RpcRequest::GetBalance;
245        let request = test_request.build_request_json(1, json!([addr]));
246        assert_eq!(request["method"], "getBalance");
247
248        let test_request = RpcRequest::GetEpochInfo;
249        let request = test_request.build_request_json(1, Value::Null);
250        assert_eq!(request["method"], "getEpochInfo");
251
252        let test_request = RpcRequest::GetLatestBlockhash;
253        let request = test_request.build_request_json(1, Value::Null);
254        assert_eq!(request["method"], "getLatestBlockhash");
255
256        let test_request = RpcRequest::GetSlot;
257        let request = test_request.build_request_json(1, Value::Null);
258        assert_eq!(request["method"], "getSlot");
259
260        let test_request = RpcRequest::GetTransactionCount;
261        let request = test_request.build_request_json(1, Value::Null);
262        assert_eq!(request["method"], "getTransactionCount");
263
264        let test_request = RpcRequest::RequestAirdrop;
265        let request = test_request.build_request_json(1, Value::Null);
266        assert_eq!(request["method"], "requestAirdrop");
267
268        let test_request = RpcRequest::SendTransaction;
269        let request = test_request.build_request_json(1, Value::Null);
270        assert_eq!(request["method"], "sendTransaction");
271
272        let test_request = RpcRequest::GetTokenLargestAccounts;
273        let request = test_request.build_request_json(1, Value::Null);
274        assert_eq!(request["method"], "getTokenLargestAccounts");
275    }
276
277    #[test]
278    fn test_build_request_json_config_options() {
279        let commitment_config = CommitmentConfig {
280            commitment: CommitmentLevel::Finalized,
281        };
282        let addr = json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx");
283
284        // Test request with CommitmentConfig and no params
285        let test_request = RpcRequest::GetLatestBlockhash;
286        let request = test_request.build_request_json(1, json!([commitment_config]));
287        assert_eq!(request["params"], json!([commitment_config.clone()]));
288
289        // Test request with CommitmentConfig and params
290        let test_request = RpcRequest::GetBalance;
291        let request = test_request.build_request_json(1, json!([addr, commitment_config]));
292        assert_eq!(request["params"], json!([addr, commitment_config]));
293
294        // Test request with CommitmentConfig and params
295        let test_request = RpcRequest::GetTokenAccountsByOwner;
296        let mint = solana_pubkey::new_rand();
297        let token_account_filter = RpcTokenAccountsFilter::Mint(mint.to_string());
298        let request = test_request
299            .build_request_json(1, json!([addr, token_account_filter, commitment_config]));
300        assert_eq!(
301            request["params"],
302            json!([addr, token_account_filter, commitment_config])
303        );
304    }
305}