solana_rpc_client_api/
request.rs

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