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