surfpool_core/rpc/
minimal.rs

1use jsonrpc_core::{BoxFuture, Result};
2use jsonrpc_derive::rpc;
3use solana_client::{
4    rpc_config::{
5        RpcContextConfig, RpcGetVoteAccountsConfig, RpcLeaderScheduleConfig,
6        RpcLeaderScheduleConfigWrapper,
7    },
8    rpc_custom_error::RpcCustomError,
9    rpc_response::{
10        RpcIdentity, RpcLeaderSchedule, RpcResponseContext, RpcSnapshotSlotInfo,
11        RpcVoteAccountStatus,
12    },
13};
14use solana_clock::Slot;
15use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
16use solana_epoch_info::EpochInfo;
17use solana_rpc_client_api::response::Response as RpcResponse;
18
19use super::{not_implemented_err, RunloopContext, SurfnetRpcContext};
20use crate::{
21    rpc::{utils::verify_pubkey, State},
22    surfnet::{locker::SvmAccessContext, GetAccountResult, FINALIZATION_SLOT_THRESHOLD},
23};
24
25const SURFPOOL_VERSION: &str = env!("CARGO_PKG_VERSION");
26
27#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
28#[serde(rename_all = "kebab-case")]
29pub struct SurfpoolRpcVersionInfo {
30    /// The current version of surfpool
31    pub surfnet_version: String,
32    /// The current version of solana-core
33    pub solana_core: String,
34    /// first 4 bytes of the FeatureSet identifier
35    pub feature_set: Option<u32>,
36}
37
38#[rpc]
39pub trait Minimal {
40    type Metadata;
41
42    /// Returns the balance (in lamports) of the account at the provided public key.
43    ///
44    /// This endpoint queries the current or historical balance of an account, depending on the optional commitment level provided in the config.
45    ///
46    /// ## Parameters
47    /// - `pubkey_str`: The base-58 encoded public key of the account to query.
48    /// - `_config` *(optional)*: [`RpcContextConfig`] specifying commitment level and/or minimum context slot.
49    ///
50    /// ## Returns
51    /// An [`RpcResponse<u64>`] where the value is the balance in lamports.
52    ///
53    /// ## Example Request
54    /// ```json
55    /// {
56    ///   "jsonrpc": "2.0",
57    ///   "id": 1,
58    ///   "method": "getBalance",
59    ///   "params": [
60    ///     "4Nd1mXUmh23rQk8VN7wM9hEnfxqrrB1yrn11eW9gMoVr"
61    ///   ]
62    /// }
63    /// ```
64    ///
65    /// ## Example Response
66    /// ```json
67    /// {
68    ///   "jsonrpc": "2.0",
69    ///   "result": {
70    ///     "context": {
71    ///       "slot": 1085597
72    ///     },
73    ///     "value": 20392800
74    ///   },
75    ///   "id": 1
76    /// }
77    /// ```
78    ///
79    /// # Notes
80    /// - 1 SOL = 1,000,000,000 lamports.
81    /// - Use commitment level in the config to specify whether the balance should be fetched from processed, confirmed, or finalized state.
82    ///
83    /// # See Also
84    /// - `getAccountInfo`, `getTokenAccountBalance`
85    #[rpc(meta, name = "getBalance")]
86    fn get_balance(
87        &self,
88        meta: Self::Metadata,
89        pubkey_str: String,
90        _config: Option<RpcContextConfig>,
91    ) -> BoxFuture<Result<RpcResponse<u64>>>;
92
93    /// Returns information about the current epoch.
94    ///
95    /// This endpoint provides epoch-related data such as the current epoch number, the total number of slots in the epoch,
96    /// the current slot index within the epoch, and the absolute slot number.
97    ///
98    /// ## Parameters
99    /// - `config` *(optional)*: [`RpcContextConfig`] for specifying commitment level and/or minimum context slot.
100    ///
101    /// ## Returns
102    /// An [`EpochInfo`] struct containing information about the current epoch.
103    ///
104    /// ## Example Request
105    /// ```json
106    /// {
107    ///   "jsonrpc": "2.0",
108    ///   "id": 1,
109    ///   "method": "getEpochInfo"
110    /// }
111    /// ```
112    ///
113    /// ## Example Response
114    /// ```json
115    /// {
116    ///   "jsonrpc": "2.0",
117    ///   "result": {
118    ///     "epoch": 278,
119    ///     "slotIndex": 423,
120    ///     "slotsInEpoch": 432000,
121    ///     "absoluteSlot": 124390823,
122    ///     "blockHeight": 18962432,
123    ///     "transactionCount": 981234523
124    ///   },
125    ///   "id": 1
126    /// }
127    /// ```
128    ///
129    /// # Notes
130    /// - The `slotIndex` is the current slot's position within the epoch.
131    /// - `slotsInEpoch` may vary due to network adjustments (e.g., warm-up periods).
132    /// - The `commitment` field in the config can influence how recent the returned data is.
133    ///
134    /// # See Also
135    /// - `getEpochSchedule`, `getSlot`, `getBlockHeight`
136    #[rpc(meta, name = "getEpochInfo")]
137    fn get_epoch_info(
138        &self,
139        meta: Self::Metadata,
140        config: Option<RpcContextConfig>,
141    ) -> Result<EpochInfo>;
142
143    /// Returns the genesis hash of the blockchain.
144    ///
145    /// The genesis hash is a unique identifier that represents the state of the blockchain at the genesis block (the very first block).
146    /// This can be used to validate the integrity of the blockchain and ensure that a node is operating with the correct blockchain data.
147    ///
148    /// ## Parameters
149    /// - None.
150    ///
151    /// ## Returns
152    /// A `String` containing the base-58 encoded genesis hash.
153    ///
154    /// ## Example Request
155    /// ```json
156    /// {
157    ///   "jsonrpc": "2.0",
158    ///   "id": 1,
159    ///   "method": "getGenesisHash"
160    /// }
161    /// ```
162    ///
163    /// ## Example Response
164    /// ```json
165    /// {
166    ///   "jsonrpc": "2.0",
167    ///   "result": "5eymX3jrWXcKqD1tsB2BzAB6gX9LP2pLrpVG6KwBSoZJ"
168    ///   "id": 1
169    /// }
170    /// ```
171    ///
172    /// # Notes
173    /// - The genesis hash is a critical identifier for validating the blockchain’s origin and initial state.
174    /// - This endpoint does not require any parameters and provides a quick way to verify the genesis hash for blockchain verification or initial setup.
175    ///
176    /// # See Also
177    /// - `getEpochInfo`, `getBlock`, `getClusterNodes`
178    #[rpc(meta, name = "getGenesisHash")]
179    fn get_genesis_hash(&self, meta: Self::Metadata) -> Result<String>;
180
181    /// Returns the health status of the blockchain node.
182    ///
183    /// This method checks the health of the node and returns a status indicating whether the node is in a healthy state
184    /// or if it is experiencing any issues such as being out of sync with the network or encountering any failures.
185    ///
186    /// ## Parameters
187    /// - None.
188    ///
189    /// ## Returns
190    /// A `String` indicating the health status of the node:
191    /// - `"ok"`: The node is healthy and synchronized with the network.
192    /// - `"failed"`: The node is not healthy or is experiencing issues.
193    ///
194    /// ## Example Request
195    /// ```json
196    /// {
197    ///   "jsonrpc": "2.0",
198    ///   "id": 1,
199    ///   "method": "getHealth"
200    /// }
201    /// ```
202    ///
203    /// ## Example Response
204    /// ```json
205    /// {
206    ///   "jsonrpc": "2.0",
207    ///   "result": "ok",
208    ///   "id": 1
209    /// }
210    /// ```
211    ///
212    /// # Notes
213    /// - The `"ok"` response means that the node is fully operational and synchronized with the blockchain.
214    /// - The `"failed"` response indicates that the node is either out of sync, has encountered an error, or is not functioning properly.
215    /// - This is typically used to monitor the health of the node in production environments.
216    ///
217    /// # See Also
218    /// - `getGenesisHash`, `getEpochInfo`, `getBlock`
219    #[rpc(meta, name = "getHealth")]
220    fn get_health(&self, meta: Self::Metadata) -> Result<String>;
221
222    /// Returns the identity (public key) of the node.
223    ///
224    /// This method retrieves the current identity of the node, which is represented by a public key.
225    /// The identity is used to uniquely identify the node on the network.
226    ///
227    /// ## Parameters
228    /// - None.
229    ///
230    /// ## Returns
231    /// A `RpcIdentity` object containing the identity of the node:
232    /// - `identity`: The base-58 encoded public key of the node's identity.
233    ///
234    /// ## Example Request
235    /// ```json
236    /// {
237    ///   "jsonrpc": "2.0",
238    ///   "id": 1,
239    ///   "method": "getIdentity"
240    /// }
241    /// ```
242    ///
243    /// ## Example Response
244    /// ```json
245    /// {
246    ///   "jsonrpc": "2.0",
247    ///   "result": {
248    ///     "identity": "Base58EncodedPublicKeyHere"
249    ///   },
250    ///   "id": 1
251    /// }
252    /// ```
253    ///
254    /// # Notes
255    /// - The identity returned is a base-58 encoded public key representing the current node.
256    /// - This identity is often used for network identification and security.
257    ///
258    /// # See Also
259    /// - `getGenesisHash`, `getHealth`, `getBlock`
260    #[rpc(meta, name = "getIdentity")]
261    fn get_identity(&self, meta: Self::Metadata) -> Result<RpcIdentity>;
262
263    /// Returns the current slot of the ledger.
264    ///
265    /// This method retrieves the current slot number in the blockchain, which represents a point in the ledger's history.
266    /// Slots are used to organize and validate the timing of transactions in the network.
267    ///
268    /// ## Parameters
269    /// - `config` (optional): Configuration options for the request, such as commitment level or context slot. Defaults to `None`.
270    ///
271    /// ## Returns
272    /// A `Slot` value representing the current slot of the ledger.
273    ///
274    /// ## Example Request
275    /// ```json
276    /// {
277    ///   "jsonrpc": "2.0",
278    ///   "id": 1,
279    ///   "method": "getSlot"
280    /// }
281    /// ```
282    ///
283    /// ## Example Response
284    /// ```json
285    /// {
286    ///   "jsonrpc": "2.0",
287    ///   "result": 12345678,
288    ///   "id": 1
289    /// }
290    /// ```
291    ///
292    /// # Notes
293    /// - The slot represents the position in the ledger. It increments over time as new blocks are produced.
294    ///
295    /// # See Also
296    /// - `getBlock`, `getEpochInfo`, `getGenesisHash`
297    #[rpc(meta, name = "getSlot")]
298    fn get_slot(&self, meta: Self::Metadata, config: Option<RpcContextConfig>) -> Result<Slot>;
299
300    /// Returns the current block height.
301    ///
302    /// This method retrieves the height of the most recent block in the ledger, which is an indicator of how many blocks have been added to the blockchain. The block height is the number of blocks that have been produced since the genesis block.
303    ///
304    /// ## Parameters
305    /// - `config` (optional): Configuration options for the request, such as commitment level or context slot. Defaults to `None`.
306    ///
307    /// ## Returns
308    /// A `u64` representing the current block height of the ledger.
309    ///
310    /// ## Example Request
311    /// ```json
312    /// {
313    ///   "jsonrpc": "2.0",
314    ///   "id": 1,
315    ///   "method": "getBlockHeight"
316    /// }
317    /// ```
318    ///
319    /// ## Example Response
320    /// ```json
321    /// {
322    ///   "jsonrpc": "2.0",
323    ///   "result": 12345678,
324    ///   "id": 1
325    /// }
326    /// ```
327    ///
328    /// # Notes
329    /// - The block height reflects the number of blocks produced in the ledger, starting from the genesis block. It is incremented each time a new block is added.
330    ///
331    /// # See Also
332    /// - `getSlot`, `getEpochInfo`, `getGenesisHash`
333    #[rpc(meta, name = "getBlockHeight")]
334    fn get_block_height(
335        &self,
336        meta: Self::Metadata,
337        config: Option<RpcContextConfig>,
338    ) -> Result<u64>;
339
340    /// Returns information about the highest snapshot slot.
341    ///
342    /// This method retrieves information about the most recent snapshot slot, which refers to the slot in the blockchain where the most recent snapshot has been taken. A snapshot is a point-in-time capture of the state of the ledger, allowing for quicker validation of the state without processing every transaction.
343    ///
344    /// ## Parameters
345    /// - `meta`: Metadata passed with the request, such as the client’s request context.
346    ///
347    /// ## Returns
348    /// A `RpcSnapshotSlotInfo` containing information about the highest snapshot slot.
349    ///
350    /// ## Example Request
351    /// ```json
352    /// {
353    ///   "jsonrpc": "2.0",
354    ///   "id": 1,
355    ///   "method": "getHighestSnapshotSlot"
356    /// }
357    /// ```
358    ///
359    /// ## Example Response
360    /// ```json
361    /// {
362    ///   "jsonrpc": "2.0",
363    ///   "result": {
364    ///     "slot": 987654,
365    ///     "root": "A9B7F1A4D1D55D0635B905E5AB6341C5D9F7F4D2A1160C53B5647B1E3259BB24"
366    ///   },
367    ///   "id": 1
368    /// }
369    /// ```
370    ///
371    /// # Notes
372    /// - The snapshot slot represents the most recent snapshot in the blockchain and is used for more efficient state validation and recovery.
373    /// - The result also includes the root, which is the blockhash at the snapshot point.
374    ///
375    /// # See Also
376    /// - `getBlock`, `getSnapshotInfo`
377    #[rpc(meta, name = "getHighestSnapshotSlot")]
378    fn get_highest_snapshot_slot(&self, meta: Self::Metadata) -> Result<RpcSnapshotSlotInfo>;
379
380    /// Returns the total number of transactions processed by the blockchain.
381    ///
382    /// This method retrieves the number of transactions that have been processed in the blockchain up to the current point. It provides a snapshot of the transaction throughput and can be useful for monitoring and performance analysis.
383    ///
384    /// ## Parameters
385    /// - `meta`: Metadata passed with the request, such as the client’s request context.
386    /// - `config`: Optional configuration for the request, such as commitment settings or minimum context slot.
387    ///
388    /// ## Returns
389    /// A `u64` representing the total transaction count.
390    ///
391    /// ## Example Request
392    /// ```json
393    /// {
394    ///   "jsonrpc": "2.0",
395    ///   "id": 1,
396    ///   "method": "getTransactionCount"
397    /// }
398    /// ```
399    ///
400    /// ## Example Response
401    /// ```json
402    /// {
403    ///   "jsonrpc": "2.0",
404    ///   "result": 1234567890,
405    ///   "id": 1
406    /// }
407    /// ```
408    ///
409    /// # Notes
410    /// - This method gives a cumulative count of all transactions in the blockchain from the start of the network.
411    ///
412    /// # See Also
413    /// - `getBlockHeight`, `getEpochInfo`
414    #[rpc(meta, name = "getTransactionCount")]
415    fn get_transaction_count(
416        &self,
417        meta: Self::Metadata,
418        config: Option<RpcContextConfig>,
419    ) -> Result<u64>;
420
421    /// Returns the current version of the server or application.
422    ///
423    /// This method retrieves the version information for the server or application. It provides details such as the version number and additional metadata that can help with compatibility checks or updates.
424    ///
425    /// ## Parameters
426    /// - `meta`: Metadata passed with the request, such as the client’s request context.
427    ///
428    /// ## Returns
429    /// A `SurfpoolRpcVersionInfo` object containing the version details.
430    ///
431    /// ## Example Request
432    /// ```json
433    /// {
434    ///   "jsonrpc": "2.0",
435    ///   "id": 1,
436    ///   "method": "getVersion"
437    /// }
438    /// ```
439    ///
440    /// ## Example Response
441    /// ```json
442    /// {
443    ///   "jsonrpc": "2.0",
444    ///   "result": {
445    ///     "surfnet_version": "1.2.3",
446    ///     "solana_core": "1.9.0",
447    ///     "feature_set": 12345
448    ///   },
449    ///   "id": 1
450    /// }
451    /// ```
452    ///
453    /// # Notes
454    /// - The version information typically includes the version number of `surfpool`, the version of `solana-core`, and a `feature_set` identifier (first 4 bytes).
455    /// - The `feature_set` field may not always be present, depending on whether a feature set identifier is available.
456    ///
457    /// # See Also
458    /// - `getHealth`, `getIdentity`
459    #[rpc(meta, name = "getVersion")]
460    fn get_version(&self, meta: Self::Metadata) -> Result<SurfpoolRpcVersionInfo>;
461
462    /// Returns vote account information.
463    ///
464    /// This method retrieves the current status of vote accounts, including information about the validator’s vote account and whether it is delinquent. The response includes vote account details such as the stake, commission, vote history, and more.
465    ///
466    /// ## Parameters
467    /// - `meta`: Metadata passed with the request, such as the client’s request context.
468    /// - `config`: Optional configuration parameters, such as specific vote account addresses or commitment settings.
469    ///
470    /// ## Returns
471    /// A `RpcVoteAccountStatus` object containing details about the current and delinquent vote accounts.
472    ///
473    /// ## Example Request
474    /// ```json
475    /// {
476    ///   "jsonrpc": "2.0",
477    ///   "id": 1,
478    ///   "method": "getVoteAccounts",
479    ///   "params": [{}]
480    /// }
481    /// ```
482    ///
483    /// ## Example Response
484    /// ```json
485    /// {
486    ///   "jsonrpc": "2.0",
487    ///   "result": {
488    ///     "current": [
489    ///       {
490    ///         "votePubkey": "votePubkeyBase58",
491    ///         "nodePubkey": "nodePubkeyBase58",
492    ///         "activatedStake": 1000000,
493    ///         "commission": 5,
494    ///         "epochVoteAccount": true,
495    ///         "epochCredits": [[1, 1000, 900], [2, 1100, 1000]],
496    ///         "lastVote": 1000,
497    ///         "rootSlot": 1200
498    ///       }
499    ///     ],
500    ///     "delinquent": [
501    ///       {
502    ///         "votePubkey": "delinquentVotePubkeyBase58",
503    ///         "nodePubkey": "delinquentNodePubkeyBase58",
504    ///         "activatedStake": 0,
505    ///         "commission": 10,
506    ///         "epochVoteAccount": false,
507    ///         "epochCredits": [[1, 500, 400]],
508    ///         "lastVote": 0,
509    ///         "rootSlot": 0
510    ///       }
511    ///     ]
512    ///   },
513    ///   "id": 1
514    /// }
515    /// ```
516    ///
517    /// # Notes
518    /// - The `current` field contains details about vote accounts that are active and have current stake.
519    /// - The `delinquent` field contains details about vote accounts that have become delinquent due to inactivity or other issues.
520    /// - The `epochCredits` field contains historical voting data.
521    ///
522    /// # See Also
523    /// - `getHealth`, `getIdentity`, `getVersion`
524    #[rpc(meta, name = "getVoteAccounts")]
525    fn get_vote_accounts(
526        &self,
527        meta: Self::Metadata,
528        config: Option<RpcGetVoteAccountsConfig>,
529    ) -> Result<RpcVoteAccountStatus>;
530
531    /// Returns the leader schedule for the given configuration or slot.
532    ///
533    /// This method retrieves the leader schedule for the given slot or configuration, providing a map of validator identities to slot indices within a given range.
534    ///
535    /// ## Parameters
536    /// - `meta`: Metadata passed with the request, such as the client’s request context.
537    /// - `options`: Optional configuration wrapper, which can either be a specific slot or a full configuration.
538    /// - `config`: Optional configuration containing the validator identity and commitment level.
539    ///
540    /// ## Returns
541    /// An `Option<RpcLeaderSchedule>` containing a map of leader identities (base-58 encoded pubkeys) to slot indices, relative to the first epoch slot.
542    ///
543    /// ## Example Request
544    /// ```json
545    /// {
546    ///   "jsonrpc": "2.0",
547    ///   "id": 1,
548    ///   "method": "getLeaderSchedule",
549    ///   "params": [{}]
550    /// }
551    /// ```
552    ///
553    /// ## Example Response
554    /// ```json
555    /// {
556    ///   "jsonrpc": "2.0",
557    ///   "result": {
558    ///     "votePubkey1": [0, 2, 4],
559    ///     "votePubkey2": [1, 3, 5]
560    ///   },
561    ///   "id": 1
562    /// }
563    /// ```
564    ///
565    /// # Notes
566    /// - The returned map contains validator identities as keys (base-58 encoded strings), with slot indices as values.
567    ///
568    /// # See Also
569    /// - `getSlot`, `getBlockHeight`, `getEpochInfo`
570    #[rpc(meta, name = "getLeaderSchedule")]
571    fn get_leader_schedule(
572        &self,
573        meta: Self::Metadata,
574        options: Option<RpcLeaderScheduleConfigWrapper>,
575        config: Option<RpcLeaderScheduleConfig>,
576    ) -> Result<Option<RpcLeaderSchedule>>;
577}
578
579#[derive(Clone)]
580pub struct SurfpoolMinimalRpc;
581impl Minimal for SurfpoolMinimalRpc {
582    type Metadata = Option<RunloopContext>;
583
584    fn get_balance(
585        &self,
586        meta: Self::Metadata,
587        pubkey_str: String,
588        _config: Option<RpcContextConfig>, // TODO: use config
589    ) -> BoxFuture<Result<RpcResponse<u64>>> {
590        let pubkey = match verify_pubkey(&pubkey_str) {
591            Ok(res) => res,
592            Err(e) => return e.into(),
593        };
594
595        let SurfnetRpcContext {
596            svm_locker,
597            remote_ctx,
598        } = match meta.get_rpc_context(CommitmentConfig::confirmed()) {
599            Ok(res) => res,
600            Err(e) => return e.into(),
601        };
602
603        Box::pin(async move {
604            let SvmAccessContext {
605                slot,
606                inner: account_update,
607                ..
608            } = svm_locker.get_account(&remote_ctx, &pubkey, None).await?;
609
610            let balance = match &account_update {
611                GetAccountResult::FoundAccount(_, account, _)
612                | GetAccountResult::FoundProgramAccount((_, account), _) => account.lamports,
613                GetAccountResult::None(_) => 0,
614            };
615
616            svm_locker.write_account_update(account_update);
617
618            Ok(RpcResponse {
619                context: RpcResponseContext::new(slot),
620                value: balance,
621            })
622        })
623    }
624
625    fn get_epoch_info(
626        &self,
627        meta: Self::Metadata,
628        _config: Option<RpcContextConfig>,
629    ) -> Result<EpochInfo> {
630        meta.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.clone())
631            .map_err(Into::into)
632    }
633
634    fn get_genesis_hash(&self, _meta: Self::Metadata) -> Result<String> {
635        not_implemented_err("get_genesis_hash")
636    }
637
638    fn get_health(&self, _meta: Self::Metadata) -> Result<String> {
639        // todo: we could check the time from the state clock and compare
640        Ok("ok".to_string())
641    }
642
643    fn get_identity(&self, _meta: Self::Metadata) -> Result<RpcIdentity> {
644        not_implemented_err("get_identity")
645    }
646
647    fn get_slot(&self, meta: Self::Metadata, config: Option<RpcContextConfig>) -> Result<Slot> {
648        let config = config.unwrap_or_default();
649        let latest_absolute_slot = meta
650            .with_svm_reader(|svm_reader| svm_reader.get_latest_absolute_slot())
651            .map_err(Into::<jsonrpc_core::Error>::into)?;
652        let slot = match config.commitment.unwrap_or_default().commitment {
653            CommitmentLevel::Processed => latest_absolute_slot,
654            CommitmentLevel::Confirmed => latest_absolute_slot - 1,
655            CommitmentLevel::Finalized => latest_absolute_slot - FINALIZATION_SLOT_THRESHOLD,
656        };
657
658        if let Some(min_context_slot) = config.min_context_slot {
659            if slot < min_context_slot {
660                return Err(RpcCustomError::MinContextSlotNotReached {
661                    context_slot: min_context_slot,
662                }
663                .into());
664            }
665        }
666
667        Ok(slot)
668    }
669
670    fn get_block_height(
671        &self,
672        meta: Self::Metadata,
673        _config: Option<RpcContextConfig>,
674    ) -> Result<u64> {
675        meta.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.block_height)
676            .map_err(Into::into)
677    }
678
679    fn get_highest_snapshot_slot(&self, _meta: Self::Metadata) -> Result<RpcSnapshotSlotInfo> {
680        not_implemented_err("get_highest_snapshot_slot")
681    }
682
683    fn get_transaction_count(
684        &self,
685        meta: Self::Metadata,
686        _config: Option<RpcContextConfig>,
687    ) -> Result<u64> {
688        meta.with_svm_reader(|svm_reader| svm_reader.transactions_processed)
689            .map_err(Into::into)
690    }
691
692    fn get_version(&self, _: Self::Metadata) -> Result<SurfpoolRpcVersionInfo> {
693        let version = solana_version::Version::default();
694
695        Ok(SurfpoolRpcVersionInfo {
696            surfnet_version: SURFPOOL_VERSION.to_string(),
697            solana_core: version.to_string(),
698            feature_set: Some(version.feature_set),
699        })
700    }
701
702    // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so
703    //       it can be removed from rpc_minimal
704    fn get_vote_accounts(
705        &self,
706        _meta: Self::Metadata,
707        _config: Option<RpcGetVoteAccountsConfig>,
708    ) -> Result<RpcVoteAccountStatus> {
709        Ok(RpcVoteAccountStatus {
710            current: vec![],
711            delinquent: vec![],
712        })
713    }
714
715    // TODO: Refactor `agave-validator wait-for-restart-window` to not require this method, so
716    //       it can be removed from rpc_minimal
717    fn get_leader_schedule(
718        &self,
719        _meta: Self::Metadata,
720        _options: Option<RpcLeaderScheduleConfigWrapper>,
721        _config: Option<RpcLeaderScheduleConfig>,
722    ) -> Result<Option<RpcLeaderSchedule>> {
723        // let (slot, maybe_config) = options.map(|options| options.unzip()).unwrap_or_default();
724        // let config = maybe_config.or(config).unwrap_or_default();
725
726        // if let Some(ref identity) = config.identity {
727        //     let _ = verify_pubkey(identity)?;
728        // }
729
730        // let bank = meta.bank(config.commitment);
731        // let slot = slot.unwrap_or_else(|| bank.slot());
732        // let epoch = bank.epoch_schedule().get_epoch(slot);
733
734        // println!("get_leader_schedule rpc request received: {:?}", slot);
735
736        // Ok(meta
737        //     .leader_schedule_cache
738        //     .get_epoch_leader_schedule(epoch)
739        //     .map(|leader_schedule| {
740        //         let mut schedule_by_identity =
741        //             solana_ledger::leader_schedule_utils::leader_schedule_by_identity(
742        //                 leader_schedule.get_slot_leaders().iter().enumerate(),
743        //             );
744        //         if let Some(identity) = config.identity {
745        //             schedule_by_identity.retain(|k, _| *k == identity);
746        //         }
747        //         schedule_by_identity
748        //     }))
749        not_implemented_err("get_leader_schedule")
750    }
751}
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756    use crate::tests::helpers::TestSetup;
757
758    #[test]
759    fn test_get_health() {
760        let setup = TestSetup::new(SurfpoolMinimalRpc);
761        let result = setup.rpc.get_health(Some(setup.context));
762        assert_eq!(result.unwrap(), "ok");
763    }
764
765    #[test]
766    fn test_get_transaction_count() {
767        let setup = TestSetup::new(SurfpoolMinimalRpc);
768        let transactions_processed = setup
769            .context
770            .svm_locker
771            .with_svm_reader(|svm_reader| svm_reader.transactions_processed);
772        let result = setup.rpc.get_transaction_count(Some(setup.context), None);
773        assert_eq!(result.unwrap(), transactions_processed);
774    }
775
776    #[test]
777    fn test_get_epoch_info() {
778        let info = EpochInfo {
779            epoch: 1,
780            slot_index: 1,
781            slots_in_epoch: 1,
782            absolute_slot: 1,
783            block_height: 1,
784            transaction_count: Some(1),
785        };
786        let setup = TestSetup::new_with_epoch_info(SurfpoolMinimalRpc, info.clone());
787        let result = setup.rpc.get_epoch_info(Some(setup.context), None).unwrap();
788        assert_eq!(result, info);
789    }
790
791    #[test]
792    fn test_get_slot() {
793        let setup = TestSetup::new(SurfpoolMinimalRpc);
794        let result = setup.rpc.get_slot(Some(setup.context), None).unwrap();
795        assert_eq!(result, 92);
796    }
797
798    #[test]
799    fn test_get_version() {
800        let setup = TestSetup::new(SurfpoolMinimalRpc);
801        let result = setup.rpc.get_version(Some(setup.context)).unwrap();
802        assert!(!result.solana_core.is_empty());
803        assert!(result.feature_set.is_some());
804        assert_eq!(result.surfnet_version, format!("{}", SURFPOOL_VERSION));
805    }
806
807    #[test]
808    fn test_get_vote_accounts() {
809        let setup = TestSetup::new(SurfpoolMinimalRpc);
810        let result = setup
811            .rpc
812            .get_vote_accounts(Some(setup.context), None)
813            .unwrap();
814        assert!(result.current.is_empty());
815        assert!(result.delinquent.is_empty());
816    }
817}