solana_rpc_client_api/
response.rs

1use {
2    crate::client_error,
3    serde::{Deserialize, Deserializer, Serialize, Serializer},
4    solana_account_decoder_client_types::{token::UiTokenAmount, UiAccount},
5    solana_sdk::{
6        clock::{Epoch, Slot, UnixTimestamp},
7        fee_calculator::{FeeCalculator, FeeRateGovernor},
8        inflation::Inflation,
9        transaction::{Result, TransactionError},
10    },
11    solana_transaction_status_client_types::{
12        ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock,
13        UiInnerInstructions, UiTransactionReturnData,
14    },
15    std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr},
16    thiserror::Error,
17};
18
19/// Wrapper for rpc return types of methods that provide responses both with and without context.
20/// Main purpose of this is to fix methods that lack context information in their return type,
21/// without breaking backwards compatibility.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(untagged)]
24pub enum OptionalContext<T> {
25    Context(Response<T>),
26    NoContext(T),
27}
28
29impl<T> OptionalContext<T> {
30    pub fn parse_value(self) -> T {
31        match self {
32            Self::Context(response) => response.value,
33            Self::NoContext(value) => value,
34        }
35    }
36}
37
38pub type RpcResult<T> = client_error::Result<Response<T>>;
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct RpcResponseContext {
43    pub slot: Slot,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub api_version: Option<RpcApiVersion>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct RpcApiVersion(semver::Version);
50
51impl std::ops::Deref for RpcApiVersion {
52    type Target = semver::Version;
53    fn deref(&self) -> &Self::Target {
54        &self.0
55    }
56}
57
58impl Default for RpcApiVersion {
59    fn default() -> Self {
60        Self(solana_version::Version::default().as_semver_version())
61    }
62}
63
64impl Serialize for RpcApiVersion {
65    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
66    where
67        S: Serializer,
68    {
69        serializer.serialize_str(&self.to_string())
70    }
71}
72
73impl<'de> Deserialize<'de> for RpcApiVersion {
74    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        let s: String = Deserialize::deserialize(deserializer)?;
79        Ok(RpcApiVersion(
80            semver::Version::from_str(&s).map_err(serde::de::Error::custom)?,
81        ))
82    }
83}
84
85impl RpcResponseContext {
86    pub fn new(slot: Slot) -> Self {
87        Self {
88            slot,
89            api_version: Some(RpcApiVersion::default()),
90        }
91    }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub struct Response<T> {
96    pub context: RpcResponseContext,
97    pub value: T,
98}
99
100#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
101#[serde(rename_all = "camelCase")]
102pub struct RpcBlockCommitment<T> {
103    pub commitment: Option<T>,
104    pub total_stake: u64,
105}
106
107#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
108#[serde(rename_all = "camelCase")]
109pub struct RpcBlockhashFeeCalculator {
110    pub blockhash: String,
111    pub fee_calculator: FeeCalculator,
112}
113
114#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
115#[serde(rename_all = "camelCase")]
116pub struct RpcBlockhash {
117    pub blockhash: String,
118    pub last_valid_block_height: u64,
119}
120
121#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
122#[serde(rename_all = "camelCase")]
123pub struct RpcFeeCalculator {
124    pub fee_calculator: FeeCalculator,
125}
126
127#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
128#[serde(rename_all = "camelCase")]
129pub struct RpcFeeRateGovernor {
130    pub fee_rate_governor: FeeRateGovernor,
131}
132
133#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
134#[serde(rename_all = "camelCase")]
135pub struct RpcInflationGovernor {
136    pub initial: f64,
137    pub terminal: f64,
138    pub taper: f64,
139    pub foundation: f64,
140    pub foundation_term: f64,
141}
142
143impl From<Inflation> for RpcInflationGovernor {
144    fn from(inflation: Inflation) -> Self {
145        Self {
146            initial: inflation.initial,
147            terminal: inflation.terminal,
148            taper: inflation.taper,
149            foundation: inflation.foundation,
150            foundation_term: inflation.foundation_term,
151        }
152    }
153}
154
155#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
156#[serde(rename_all = "camelCase")]
157pub struct RpcInflationRate {
158    pub total: f64,
159    pub validator: f64,
160    pub foundation: f64,
161    pub epoch: Epoch,
162}
163
164#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
165#[serde(rename_all = "camelCase")]
166pub struct RpcKeyedAccount {
167    pub pubkey: String,
168    pub account: UiAccount,
169}
170
171#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
172pub struct SlotInfo {
173    pub slot: Slot,
174    pub parent: Slot,
175    pub root: Slot,
176}
177
178#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
179#[serde(rename_all = "camelCase")]
180pub struct SlotTransactionStats {
181    pub num_transaction_entries: u64,
182    pub num_successful_transactions: u64,
183    pub num_failed_transactions: u64,
184    pub max_transactions_per_entry: u64,
185}
186
187#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
188#[serde(rename_all = "camelCase", tag = "type")]
189pub enum SlotUpdate {
190    FirstShredReceived {
191        slot: Slot,
192        timestamp: u64,
193    },
194    Completed {
195        slot: Slot,
196        timestamp: u64,
197    },
198    CreatedBank {
199        slot: Slot,
200        parent: Slot,
201        timestamp: u64,
202    },
203    Frozen {
204        slot: Slot,
205        timestamp: u64,
206        stats: SlotTransactionStats,
207    },
208    Dead {
209        slot: Slot,
210        timestamp: u64,
211        err: String,
212    },
213    OptimisticConfirmation {
214        slot: Slot,
215        timestamp: u64,
216    },
217    Root {
218        slot: Slot,
219        timestamp: u64,
220    },
221}
222
223impl SlotUpdate {
224    pub fn slot(&self) -> Slot {
225        match self {
226            Self::FirstShredReceived { slot, .. } => *slot,
227            Self::Completed { slot, .. } => *slot,
228            Self::CreatedBank { slot, .. } => *slot,
229            Self::Frozen { slot, .. } => *slot,
230            Self::Dead { slot, .. } => *slot,
231            Self::OptimisticConfirmation { slot, .. } => *slot,
232            Self::Root { slot, .. } => *slot,
233        }
234    }
235}
236
237#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
238#[serde(rename_all = "camelCase", untagged)]
239pub enum RpcSignatureResult {
240    ProcessedSignature(ProcessedSignatureResult),
241    ReceivedSignature(ReceivedSignatureResult),
242}
243
244#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
245#[serde(rename_all = "camelCase")]
246pub struct RpcLogsResponse {
247    pub signature: String, // Signature as base58 string
248    pub err: Option<TransactionError>,
249    pub logs: Vec<String>,
250}
251
252#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
253#[serde(rename_all = "camelCase")]
254pub struct ProcessedSignatureResult {
255    pub err: Option<TransactionError>,
256}
257
258#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
259#[serde(rename_all = "camelCase")]
260pub enum ReceivedSignatureResult {
261    ReceivedSignature,
262}
263
264#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
265#[serde(rename_all = "camelCase")]
266pub struct RpcContactInfo {
267    /// Pubkey of the node as a base-58 string
268    pub pubkey: String,
269    /// Gossip port
270    pub gossip: Option<SocketAddr>,
271    /// Tvu UDP port
272    pub tvu: Option<SocketAddr>,
273    /// Tpu UDP port
274    pub tpu: Option<SocketAddr>,
275    /// Tpu QUIC port
276    pub tpu_quic: Option<SocketAddr>,
277    /// Tpu UDP forwards port
278    pub tpu_forwards: Option<SocketAddr>,
279    /// Tpu QUIC forwards port
280    pub tpu_forwards_quic: Option<SocketAddr>,
281    /// Tpu UDP vote port
282    pub tpu_vote: Option<SocketAddr>,
283    /// Server repair UDP port
284    pub serve_repair: Option<SocketAddr>,
285    /// JSON RPC port
286    pub rpc: Option<SocketAddr>,
287    /// WebSocket PubSub port
288    pub pubsub: Option<SocketAddr>,
289    /// Software version
290    pub version: Option<String>,
291    /// First 4 bytes of the FeatureSet identifier
292    pub feature_set: Option<u32>,
293    /// Shred version
294    pub shred_version: Option<u16>,
295}
296
297/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
298pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
299
300#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct RpcBlockProductionRange {
303    pub first_slot: Slot,
304    pub last_slot: Slot,
305}
306
307#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
308#[serde(rename_all = "camelCase")]
309pub struct RpcBlockProduction {
310    /// Map of leader base58 identity pubkeys to a tuple of `(number of leader slots, number of blocks produced)`
311    pub by_identity: HashMap<String, (usize, usize)>,
312    pub range: RpcBlockProductionRange,
313}
314
315#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
316#[serde(rename_all = "kebab-case")]
317pub struct RpcVersionInfo {
318    /// The current version of solana-core
319    pub solana_core: String,
320    /// first 4 bytes of the FeatureSet identifier
321    pub feature_set: Option<u32>,
322}
323
324impl fmt::Debug for RpcVersionInfo {
325    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326        write!(f, "{}", self.solana_core)
327    }
328}
329
330impl fmt::Display for RpcVersionInfo {
331    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
332        if let Some(version) = self.solana_core.split_whitespace().next() {
333            // Display just the semver if possible
334            write!(f, "{version}")
335        } else {
336            write!(f, "{}", self.solana_core)
337        }
338    }
339}
340
341#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
342#[serde(rename_all = "kebab-case")]
343pub struct RpcIdentity {
344    /// The current node identity pubkey
345    pub identity: String,
346}
347
348#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
349#[serde(rename_all = "camelCase")]
350pub struct RpcVote {
351    /// Vote account address, as base-58 encoded string
352    pub vote_pubkey: String,
353    pub slots: Vec<Slot>,
354    pub hash: String,
355    pub timestamp: Option<UnixTimestamp>,
356    pub signature: String,
357}
358
359#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
360#[serde(rename_all = "camelCase")]
361pub struct RpcVoteAccountStatus {
362    pub current: Vec<RpcVoteAccountInfo>,
363    pub delinquent: Vec<RpcVoteAccountInfo>,
364}
365
366#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
367#[serde(rename_all = "camelCase")]
368pub struct RpcVoteAccountInfo {
369    /// Vote account address, as base-58 encoded string
370    pub vote_pubkey: String,
371
372    /// The validator identity, as base-58 encoded string
373    pub node_pubkey: String,
374
375    /// The current stake, in lamports, delegated to this vote account
376    pub activated_stake: u64,
377
378    /// An 8-bit integer used as a fraction (commission/MAX_U8) for rewards payout
379    pub commission: u8,
380
381    /// Whether this account is staked for the current epoch
382    pub epoch_vote_account: bool,
383
384    /// Latest history of earned credits for up to `MAX_RPC_VOTE_ACCOUNT_INFO_EPOCH_CREDITS_HISTORY` epochs
385    ///   each tuple is (Epoch, credits, prev_credits)
386    pub epoch_credits: Vec<(Epoch, u64, u64)>,
387
388    /// Most recent slot voted on by this vote account (0 if no votes exist)
389    pub last_vote: u64,
390
391    /// Current root slot for this vote account (0 if no root slot exists)
392    pub root_slot: Slot,
393}
394
395#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
396#[serde(rename_all = "camelCase")]
397pub struct RpcSignatureConfirmation {
398    pub confirmations: usize,
399    pub status: Result<()>,
400}
401
402#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
403#[serde(rename_all = "camelCase")]
404pub struct RpcSimulateTransactionResult {
405    pub err: Option<TransactionError>,
406    pub logs: Option<Vec<String>>,
407    pub accounts: Option<Vec<Option<UiAccount>>>,
408    pub units_consumed: Option<u64>,
409    pub return_data: Option<UiTransactionReturnData>,
410    pub inner_instructions: Option<Vec<UiInnerInstructions>>,
411    pub replacement_blockhash: Option<RpcBlockhash>,
412}
413
414#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
415#[serde(rename_all = "camelCase")]
416pub struct RpcStorageTurn {
417    pub blockhash: String,
418    pub slot: Slot,
419}
420
421#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
422#[serde(rename_all = "camelCase")]
423pub struct RpcAccountBalance {
424    pub address: String,
425    pub lamports: u64,
426}
427
428#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
429#[serde(rename_all = "camelCase")]
430pub struct RpcSupply {
431    pub total: u64,
432    pub circulating: u64,
433    pub non_circulating: u64,
434    pub non_circulating_accounts: Vec<String>,
435}
436
437#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
438#[serde(rename_all = "camelCase")]
439pub enum StakeActivationState {
440    Activating,
441    Active,
442    Deactivating,
443    Inactive,
444}
445
446#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
447#[serde(rename_all = "camelCase")]
448pub struct RpcTokenAccountBalance {
449    pub address: String,
450    #[serde(flatten)]
451    pub amount: UiTokenAmount,
452}
453
454#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
455#[serde(rename_all = "camelCase")]
456pub struct RpcConfirmedTransactionStatusWithSignature {
457    pub signature: String,
458    pub slot: Slot,
459    pub err: Option<TransactionError>,
460    pub memo: Option<String>,
461    pub block_time: Option<UnixTimestamp>,
462    pub confirmation_status: Option<TransactionConfirmationStatus>,
463}
464
465#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct RpcPerfSample {
468    pub slot: Slot,
469    pub num_transactions: u64,
470    pub num_non_vote_transactions: Option<u64>,
471    pub num_slots: u64,
472    pub sample_period_secs: u16,
473}
474
475#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct RpcInflationReward {
478    pub epoch: Epoch,
479    pub effective_slot: Slot,
480    pub amount: u64,            // lamports
481    pub post_balance: u64,      // lamports
482    pub commission: Option<u8>, // Vote account commission when the reward was credited
483}
484
485#[derive(Clone, Deserialize, Serialize, Debug, Error, Eq, PartialEq)]
486pub enum RpcBlockUpdateError {
487    #[error("block store error")]
488    BlockStoreError,
489
490    #[error("unsupported transaction version ({0})")]
491    UnsupportedTransactionVersion(u8),
492}
493
494#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
495#[serde(rename_all = "camelCase")]
496pub struct RpcBlockUpdate {
497    pub slot: Slot,
498    pub block: Option<UiConfirmedBlock>,
499    pub err: Option<RpcBlockUpdateError>,
500}
501
502impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
503    fn from(value: ConfirmedTransactionStatusWithSignature) -> Self {
504        let ConfirmedTransactionStatusWithSignature {
505            signature,
506            slot,
507            err,
508            memo,
509            block_time,
510        } = value;
511        Self {
512            signature: signature.to_string(),
513            slot,
514            err,
515            memo,
516            block_time,
517            confirmation_status: None,
518        }
519    }
520}
521
522#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
523pub struct RpcSnapshotSlotInfo {
524    pub full: Slot,
525    pub incremental: Option<Slot>,
526}
527
528#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
529#[serde(rename_all = "camelCase")]
530pub struct RpcPrioritizationFee {
531    pub slot: Slot,
532    pub prioritization_fee: u64,
533}
534
535#[cfg(test)]
536pub mod tests {
537
538    use {super::*, serde_json::json};
539
540    // Make sure that `RpcPerfSample` can read previous version JSON, one without the
541    // `num_non_vote_transactions` field.
542    #[test]
543    fn rpc_perf_sample_deserialize_old() {
544        let slot = 424;
545        let num_transactions = 2597;
546        let num_slots = 2783;
547        let sample_period_secs = 398;
548
549        let input = json!({
550            "slot": slot,
551            "numTransactions": num_transactions,
552            "numSlots": num_slots,
553            "samplePeriodSecs": sample_period_secs,
554        })
555        .to_string();
556
557        let actual: RpcPerfSample =
558            serde_json::from_str(&input).expect("Can parse RpcPerfSample from string as JSON");
559        let expected = RpcPerfSample {
560            slot,
561            num_transactions,
562            num_non_vote_transactions: None,
563            num_slots,
564            sample_period_secs,
565        };
566
567        assert_eq!(actual, expected);
568    }
569
570    // Make sure that `RpcPerfSample` serializes into the new `num_non_vote_transactions` field.
571    #[test]
572    fn rpc_perf_sample_serializes_num_non_vote_transactions() {
573        let slot = 1286;
574        let num_transactions = 1732;
575        let num_non_vote_transactions = Some(757);
576        let num_slots = 393;
577        let sample_period_secs = 197;
578
579        let input = RpcPerfSample {
580            slot,
581            num_transactions,
582            num_non_vote_transactions,
583            num_slots,
584            sample_period_secs,
585        };
586        let actual =
587            serde_json::to_value(input).expect("Can convert RpcPerfSample into a JSON value");
588        let expected = json!({
589            "slot": slot,
590            "numTransactions": num_transactions,
591            "numNonVoteTransactions": num_non_vote_transactions,
592            "numSlots": num_slots,
593            "samplePeriodSecs": sample_period_secs,
594        });
595
596        assert_eq!(actual, expected);
597    }
598}