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#[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, 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 pub pubkey: String,
267 pub gossip: Option<SocketAddr>,
269 pub tvu: Option<SocketAddr>,
271 pub tpu: Option<SocketAddr>,
273 pub tpu_quic: Option<SocketAddr>,
275 pub tpu_forwards: Option<SocketAddr>,
277 pub tpu_forwards_quic: Option<SocketAddr>,
279 pub tpu_vote: Option<SocketAddr>,
281 pub serve_repair: Option<SocketAddr>,
283 pub rpc: Option<SocketAddr>,
285 pub pubsub: Option<SocketAddr>,
287 pub version: Option<String>,
289 pub feature_set: Option<u32>,
291 pub shred_version: Option<u16>,
293}
294
295pub 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 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 pub solana_core: String,
318 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 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 pub identity: String,
344}
345
346#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
347#[serde(rename_all = "camelCase")]
348pub struct RpcVote {
349 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 pub vote_pubkey: String,
369
370 pub node_pubkey: String,
372
373 pub activated_stake: u64,
375
376 pub commission: u8,
378
379 pub epoch_vote_account: bool,
381
382 pub epoch_credits: Vec<(Epoch, u64, u64)>,
385
386 pub last_vote: u64,
388
389 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, pub post_balance: u64, pub commission: Option<u8>, }
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 #[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 #[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}