safecoin_client/
rpc_response.rs

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