solana_rpc_client_api/
request.rs

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