Skip to main content

solana_rpc_client_types/
response.rs

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