surfpool_core/rpc/bank_data.rs
1use jsonrpc_core::Result;
2use jsonrpc_derive::rpc;
3use solana_client::{
4 rpc_config::{RpcBlockProductionConfig, RpcContextConfig},
5 rpc_custom_error::RpcCustomError,
6 rpc_response::{
7 RpcBlockProduction, RpcInflationGovernor, RpcInflationRate, RpcResponseContext,
8 },
9};
10use solana_clock::Slot;
11use solana_commitment_config::CommitmentConfig;
12use solana_epoch_schedule::EpochSchedule;
13use solana_rpc_client_api::response::Response as RpcResponse;
14
15use super::{RunloopContext, State};
16use crate::SURFPOOL_IDENTITY_PUBKEY;
17
18#[rpc]
19pub trait BankData {
20 type Metadata;
21
22 /// Returns the minimum balance required for rent exemption based on the given data length.
23 ///
24 /// This RPC method calculates the minimum balance required for an account to be exempt from
25 /// rent charges. It uses the data length of the account to determine the balance. The result
26 /// can help users manage their accounts by ensuring they have enough balance to cover rent
27 /// exemption, preventing accounts from being purged by the system.
28 ///
29 /// ## Parameters
30 /// - `data_len`: The length (in bytes) of the account data. This is used to determine the
31 /// minimum balance required for rent exemption.
32 /// - `commitment`: (Optional) A `CommitmentConfig` that allows specifying the level of
33 /// commitment for querying. If not provided, the default commitment level will be used.
34 ///
35 /// ## Returns
36 /// - `Result<u64>`: The method returns the minimum balance required for rent exemption
37 /// as a `u64`. If successful, it will be wrapped in `Ok`, otherwise an error will be
38 /// returned.
39 ///
40 /// ## Example Request (JSON-RPC)
41 /// ```json
42 /// {
43 /// "jsonrpc": "2.0",
44 /// "id": 1,
45 /// "method": "getMinimumBalanceForRentExemption",
46 /// "params": [128]
47 /// }
48 /// ```
49 ///
50 /// ## Example Response
51 /// ```json
52 /// {
53 /// "jsonrpc": "2.0",
54 /// "result": 2039280,
55 /// "id": 1
56 /// }
57 /// ```
58 ///
59 /// # Notes
60 /// - This method is commonly used to determine the required balance when creating new accounts
61 /// or performing account setup operations that need rent exemption.
62 /// - The `commitment` parameter allows users to specify the level of assurance they want
63 /// regarding the state of the ledger. For example, using `Confirmed` or `Finalized` ensures
64 /// that the state is more reliable.
65 ///
66 /// ## Errors
67 /// - If there is an issue with the `data_len` or `commitment` parameter (e.g., invalid data),
68 /// an error will be returned.
69 #[rpc(meta, name = "getMinimumBalanceForRentExemption")]
70 fn get_minimum_balance_for_rent_exemption(
71 &self,
72 meta: Self::Metadata,
73 data_len: usize,
74 commitment: Option<CommitmentConfig>,
75 ) -> Result<u64>;
76
77 /// Retrieves the inflation governor settings for the network.
78 ///
79 /// This RPC method returns the current inflation governor configuration, which controls the
80 /// inflation rate of the network. The inflation governor is responsible for adjusting the
81 /// inflation rate over time, with parameters like the initial and terminal inflation rates,
82 /// the taper rate, the foundation amount, and the foundation term.
83 ///
84 /// ## Parameters
85 /// - `commitment`: (Optional) A `CommitmentConfig` that specifies the commitment level for
86 /// querying the inflation governor settings. If not provided, the default commitment level
87 /// is used. Valid commitment levels include `Processed`, `Confirmed`, or `Finalized`.
88 ///
89 /// ## Returns
90 /// - `Result<RpcInflationGovernor>`: The method returns an `RpcInflationGovernor` struct that
91 /// contains the inflation parameters if successful. Otherwise, an error will be returned.
92 ///
93 /// ## Example Request (JSON-RPC)
94 /// ```json
95 /// {
96 /// "jsonrpc": "2.0",
97 /// "id": 1,
98 /// "method": "getInflationGovernor",
99 /// "params": []
100 /// }
101 /// ```
102 ///
103 /// ## Example Response
104 /// ```json
105 /// {
106 /// "jsonrpc": "2.0",
107 /// "result": {
108 /// "initial": 0.15,
109 /// "terminal": 0.05,
110 /// "taper": 0.9,
111 /// "foundation": 0.02,
112 /// "foundation_term": 5.0
113 /// },
114 /// "id": 1
115 /// }
116 /// ```
117 ///
118 /// # Notes
119 /// - The inflation governor defines how inflation changes over time, ensuring the network's
120 /// growth remains stable and sustainable.
121 /// - The `commitment` parameter allows users to define how strongly they want to ensure the
122 /// inflation data is confirmed or finalized when queried. For example, using `Confirmed` or
123 /// `Finalized` ensures a more reliable inflation state.
124 ///
125 /// ## Errors
126 /// - If there is an issue with the `commitment` parameter or an internal error occurs,
127 /// an error will be returned.
128 #[rpc(meta, name = "getInflationGovernor")]
129 fn get_inflation_governor(
130 &self,
131 meta: Self::Metadata,
132 commitment: Option<CommitmentConfig>,
133 ) -> Result<RpcInflationGovernor>;
134
135 /// Retrieves the current inflation rate for the network.
136 ///
137 /// This RPC method returns the current inflation rate, including the breakdown of inflation
138 /// allocated to different entities such as validators and the foundation, along with the current
139 /// epoch during which the rate applies.
140 ///
141 /// ## Parameters
142 /// - No parameters are required for this method.
143 ///
144 /// ## Returns
145 /// - `Result<RpcInflationRate>`: The method returns an `RpcInflationRate` struct that contains
146 /// the total inflation rate, the validator portion, the foundation portion, and the epoch
147 /// during which this inflation rate applies.
148 ///
149 /// ## Example Request (JSON-RPC)
150 /// ```json
151 /// {
152 /// "jsonrpc": "2.0",
153 /// "id": 1,
154 /// "method": "getInflationRate",
155 /// "params": []
156 /// }
157 /// ```
158 ///
159 /// ## Example Response
160 /// ```json
161 /// {
162 /// "jsonrpc": "2.0",
163 /// "result": {
164 /// "total": 0.10,
165 /// "validator": 0.07,
166 /// "foundation": 0.03,
167 /// "epoch": 1500
168 /// },
169 /// "id": 1
170 /// }
171 /// ```
172 ///
173 /// # Notes
174 /// - The total inflation rate is distributed among validators and the foundation based on
175 /// the configuration defined in the inflation governor.
176 /// - The epoch field indicates the current epoch number during which this inflation rate applies.
177 /// An epoch is a period during which the network operates under certain parameters.
178 /// - Inflation rates can change over time depending on network conditions and governance decisions.
179 ///
180 /// ## Errors
181 /// - If there is an internal error, or if the RPC request is malformed, an error will be returned.
182 #[rpc(meta, name = "getInflationRate")]
183 fn get_inflation_rate(&self, meta: Self::Metadata) -> Result<RpcInflationRate>;
184
185 /// Retrieves the epoch schedule for the network.
186 ///
187 /// This RPC method returns the configuration for the network's epoch schedule, including
188 /// details on the number of slots per epoch, leader schedule offsets, and epoch warmup.
189 ///
190 /// ## Parameters
191 /// - No parameters are required for this method.
192 ///
193 /// ## Returns
194 /// - `Result<EpochSchedule>`: The method returns an `EpochSchedule` struct, which contains
195 /// information about the slots per epoch, leader schedule offsets, warmup state, and the
196 /// first epoch after the warmup period.
197 ///
198 /// ## Example Request (JSON-RPC)
199 /// ```json
200 /// {
201 /// "jsonrpc": "2.0",
202 /// "id": 1,
203 /// "method": "getEpochSchedule",
204 /// "params": []
205 /// }
206 /// ```
207 ///
208 /// ## Example Response
209 /// ```json
210 /// {
211 /// "jsonrpc": "2.0",
212 /// "result": {
213 /// "slotsPerEpoch": 432000,
214 /// "leaderScheduleSlotOffset": 500,
215 /// "warmup": true,
216 /// "firstNormalEpoch": 8,
217 /// "firstNormalSlot": 1073741824
218 /// },
219 /// "id": 1
220 /// }
221 /// ```
222 ///
223 /// # Notes
224 /// - The `slots_per_epoch` defines the maximum number of slots in each epoch, which determines
225 /// the number of time slots available for network validators to produce blocks.
226 /// - The `leader_schedule_slot_offset` specifies how many slots before an epoch’s start the leader
227 /// schedule calculation begins for that epoch.
228 /// - The `warmup` field indicates whether the epochs start short and grow over time.
229 /// - The `first_normal_epoch` marks the first epoch after the warmup period.
230 /// - The `first_normal_slot` gives the first slot after the warmup period in terms of the number of slots
231 /// from the start of the network.
232 ///
233 /// ## Errors
234 /// - If the RPC request is malformed, or if there is an internal error, an error will be returned.
235 #[rpc(meta, name = "getEpochSchedule")]
236 fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result<EpochSchedule>;
237
238 /// Retrieves the leader of the current slot.
239 ///
240 /// This RPC method returns the leader for the current slot in the Solana network. The leader is responsible
241 /// for producing blocks for the current slot. The leader is selected based on the Solana consensus mechanism.
242 ///
243 /// ## Parameters
244 /// - `config`: An optional configuration for the request, which can include:
245 /// - `commitment`: A commitment level that defines how "final" the data must be.
246 /// - `min_context_slot`: An optional parameter to specify a minimum slot for the request.
247 ///
248 /// ## Returns
249 /// - `Result<String>`: The method returns a `String` representing the public key of the leader for the current slot.
250 ///
251 /// ## Example Request (JSON-RPC)
252 /// ```json
253 /// {
254 /// "jsonrpc": "2.0",
255 /// "id": 1,
256 /// "method": "getSlotLeader",
257 /// "params": []
258 /// }
259 /// ```
260 ///
261 /// ## Example Response
262 /// ```json
263 /// {
264 /// "jsonrpc": "2.0",
265 /// "result": "3HgA9r8H9z5Pb2L6Pt5Yq1QoFwgr6YwdKKUh9n2ANp5U"
266 /// }
267 /// ```
268 ///
269 /// # Notes
270 /// - The leader for a given slot is selected based on the Solana network's consensus mechanism, and this method
271 /// allows you to query the current leader.
272 #[rpc(meta, name = "getSlotLeader")]
273 fn get_slot_leader(
274 &self,
275 meta: Self::Metadata,
276 config: Option<RpcContextConfig>,
277 ) -> Result<String>;
278
279 /// Retrieves the leaders for a specified range of slots.
280 ///
281 /// This RPC method returns the leaders for a specified range of slots in the Solana network. You can
282 /// specify the `start_slot` from which the leaders should be queried and limit the number of results
283 /// with the `limit` parameter. The leaders are responsible for producing blocks in the respective slots.
284 ///
285 /// ## Parameters
286 /// - `start_slot`: The starting slot number for which the leaders should be queried.
287 /// - `limit`: The number of slots (starting from `start_slot`) for which the leaders should be retrieved.
288 ///
289 /// ## Returns
290 /// - `Result<Vec<String>>`: A vector of `String` values representing the public keys of the leaders for
291 /// the specified slot range.
292 ///
293 /// ## Example Request (JSON-RPC)
294 /// ```json
295 /// {
296 /// "jsonrpc": "2.0",
297 /// "id": 1,
298 /// "method": "getSlotLeaders",
299 /// "params": [1000, 5]
300 /// }
301 /// ```
302 ///
303 /// ## Example Response
304 /// ```json
305 /// {
306 /// "jsonrpc": "2.0",
307 /// "result": [
308 /// "3HgA9r8H9z5Pb2L6Pt5Yq1QoFwgr6YwdKKUh9n2ANp5U",
309 /// "BBh1FwXts8EZY6rPZ5kS2ygq99wYjFd5K5daRjc7eF9X",
310 /// "4XYo7yP5J2J8sLNSW3wGYPk3mdS1rbZUy4oFCp7wH1DN",
311 /// "8v1Cp6sHZh8XfGWS7sHZczH3v9NxdgMbo3g91Sh88dcJ",
312 /// "N6bPqwEoD9StS4AnzE27rHyz47tPcsZQjvW9w8p2NhF7"
313 /// ]
314 /// }
315 /// ```
316 ///
317 /// # Notes
318 /// - The leaders are returned in the order corresponding to the slots queried, starting from `start_slot`
319 /// and continuing for `limit` slots.
320 /// - This method provides an efficient way to get multiple leaders for a range of slots, useful for tracking
321 /// leaders over time or for scheduling purposes in decentralized applications.
322 #[rpc(meta, name = "getSlotLeaders")]
323 fn get_slot_leaders(
324 &self,
325 meta: Self::Metadata,
326 start_slot: Slot,
327 limit: u64,
328 ) -> Result<Vec<String>>;
329
330 /// Retrieves block production information for the specified validator identity or range of slots.
331 ///
332 /// This RPC method returns block production details for a given validator identity or a range of slots
333 /// within a certain epoch. If no `identity` is provided, the method returns block production data for all
334 /// validators. If a `range` is provided, it will return block production information for the slots within
335 /// the specified range.
336 ///
337 /// ## Parameters
338 /// - `config`: An optional configuration object that can include:
339 /// - `identity`: The base-58 encoded public key of a validator to query for block production data. If `None`, results for all validators will be returned.
340 /// - `range`: A range of slots for which block production information is needed. The range will default to the current epoch if `None`.
341 /// - `commitment`: The commitment level (optional) to use when querying for the block production data.
342 ///
343 /// ## Returns
344 /// - `Result<RpcResponse<RpcBlockProduction>>`: The result contains a response object with block production data, including the number of leader slots and blocks produced by each validator.
345 ///
346 /// ## Example Request (JSON-RPC)
347 /// ```json
348 /// {
349 /// "jsonrpc": "2.0",
350 /// "id": 1,
351 /// "method": "getBlockProduction",
352 /// "params": [{
353 /// "identity": "3HgA9r8H9z5Pb2L6Pt5Yq1QoFwgr6YwdKKUh9n2ANp5U",
354 /// "range": {
355 /// "firstSlot": 1000,
356 /// "lastSlot": 1050
357 /// }
358 /// }]
359 /// }
360 /// ```
361 ///
362 /// ## Example Response
363 /// ```json
364 /// {
365 /// "jsonrpc": "2.0",
366 /// "result": {
367 /// "byIdentity": {
368 /// "3HgA9r8H9z5Pb2L6Pt5Yq1QoFwgr6YwdKKUh9n2ANp5U": [10, 8],
369 /// "BBh1FwXts8EZY6rPZ5kS2ygq99wYjFd5K5daRjc7eF9X": [5, 4]
370 /// },
371 /// "range": {
372 /// "firstSlot": 1000,
373 /// "lastSlot": 1050
374 /// }
375 /// }
376 /// }
377 /// ```
378 ///
379 /// # Notes
380 /// - The response contains a map of validator identities to a tuple of two values:
381 /// - The first value is the number of leader slots.
382 /// - The second value is the number of blocks produced by that validator in the queried range.
383 /// - The `range` object specifies the range of slots that the block production information applies to, with `first_slot` being the starting slot and `last_slot` being the optional ending slot.
384 ///
385 /// ## Example Response Interpretation
386 /// - In the example response, the identity `3HgA9r8H9z5Pb2L6Pt5Yq1QoFwgr6YwdKKUh9n2ANp5U` produced 10 leader slots and 8 blocks between slots 1000 and 1050.
387 /// - Similarly, `BBh1FwXts8EZY6rPZ5kS2ygq99wYjFd5K5daRjc7eF9X` produced 5 leader slots and 4 blocks in the same slot range.
388 #[rpc(meta, name = "getBlockProduction")]
389 fn get_block_production(
390 &self,
391 meta: Self::Metadata,
392 config: Option<RpcBlockProductionConfig>,
393 ) -> Result<RpcResponse<RpcBlockProduction>>;
394}
395
396#[derive(Clone)]
397pub struct SurfpoolBankDataRpc;
398impl BankData for SurfpoolBankDataRpc {
399 type Metadata = Option<RunloopContext>;
400
401 fn get_minimum_balance_for_rent_exemption(
402 &self,
403 meta: Self::Metadata,
404 data_len: usize,
405 _commitment: Option<CommitmentConfig>,
406 ) -> Result<u64> {
407 meta.with_svm_reader(move |svm_reader| {
408 svm_reader
409 .inner
410 .minimum_balance_for_rent_exemption(data_len)
411 })
412 .map_err(Into::into)
413 }
414
415 fn get_inflation_governor(
416 &self,
417 meta: Self::Metadata,
418 _commitment: Option<CommitmentConfig>,
419 ) -> Result<RpcInflationGovernor> {
420 meta.with_svm_reader(|svm_reader| svm_reader.inflation.into())
421 .map_err(Into::into)
422 }
423
424 fn get_inflation_rate(&self, meta: Self::Metadata) -> Result<RpcInflationRate> {
425 meta.with_svm_reader(|svm_reader| -> RpcInflationRate {
426 let inflation_activation_slot = svm_reader
427 .blocks
428 .keys()
429 .unwrap_or_default()
430 .into_iter()
431 .min()
432 .unwrap_or_default();
433 let epoch_schedule = svm_reader.inner.get_sysvar::<EpochSchedule>();
434 let inflation_start_slot = epoch_schedule.get_first_slot_in_epoch(
435 epoch_schedule
436 .get_epoch(inflation_activation_slot)
437 .saturating_sub(1),
438 );
439 let epoch = svm_reader.latest_epoch_info().epoch;
440 let num_slots = epoch_schedule.get_first_slot_in_epoch(epoch) - inflation_start_slot;
441
442 let inflation = svm_reader.inflation;
443 let slots_per_year = svm_reader.genesis_config.slots_per_year();
444
445 let slot_in_year = num_slots as f64 / slots_per_year;
446
447 RpcInflationRate {
448 total: inflation.total(slot_in_year),
449 validator: inflation.validator(slot_in_year),
450 foundation: inflation.foundation(slot_in_year),
451 epoch,
452 }
453 })
454 .map_err(Into::into)
455 }
456
457 fn get_epoch_schedule(&self, meta: Self::Metadata) -> Result<EpochSchedule> {
458 meta.with_svm_reader(move |svm_reader| svm_reader.inner.get_sysvar::<EpochSchedule>())
459 .map_err(Into::into)
460 }
461
462 fn get_slot_leader(
463 &self,
464 meta: Self::Metadata,
465 config: Option<RpcContextConfig>,
466 ) -> Result<String> {
467 let svm_locker = meta.get_svm_locker()?;
468 let config = config.unwrap_or_default();
469
470 let committed_slot =
471 svm_locker.get_slot_for_commitment(&config.commitment.unwrap_or_default());
472
473 // validate minContextSlot if provided
474 if let Some(min_context_slot) = config.min_context_slot {
475 if committed_slot < min_context_slot {
476 return Err(RpcCustomError::MinContextSlotNotReached {
477 context_slot: min_context_slot,
478 }
479 .into());
480 }
481 }
482
483 Ok(SURFPOOL_IDENTITY_PUBKEY.to_string())
484 }
485
486 fn get_slot_leaders(
487 &self,
488 meta: Self::Metadata,
489 start_slot: Slot,
490 limit: u64,
491 ) -> Result<Vec<String>> {
492 if limit > 5000 {
493 return Err(jsonrpc_core::Error {
494 code: jsonrpc_core::ErrorCode::InvalidParams,
495 message: "Limit must be less than 5000".to_string(),
496 data: None,
497 });
498 }
499
500 let svm_locker = meta.get_svm_locker()?;
501 let epoch_info = svm_locker.get_epoch_info();
502
503 let first_slot_in_epoch = epoch_info
504 .absolute_slot
505 .saturating_sub(epoch_info.slot_index);
506 let last_slot_in_epoch = first_slot_in_epoch + epoch_info.slots_in_epoch.saturating_sub(1);
507 if start_slot > last_slot_in_epoch || (start_slot + limit) > last_slot_in_epoch {
508 return Err(jsonrpc_core::Error {
509 code: jsonrpc_core::ErrorCode::InvalidParams,
510 message: format!(
511 "Invalid slot range: leader schedule for epoch {} is unavailable",
512 epoch_info.epoch
513 ),
514 data: None,
515 });
516 }
517
518 Ok(vec![SURFPOOL_IDENTITY_PUBKEY.to_string()])
519 }
520
521 fn get_block_production(
522 &self,
523 meta: Self::Metadata,
524 config: Option<RpcBlockProductionConfig>,
525 ) -> Result<RpcResponse<RpcBlockProduction>> {
526 meta.with_svm_reader(|svm_reader| {
527 let current_slot = svm_reader.get_latest_absolute_slot();
528 let epoch_info = &svm_reader.latest_epoch_info;
529
530 let (first_slot, last_slot) = if let Some(ref config) = config {
531 if let Some(ref range) = config.range {
532 (range.first_slot, range.last_slot.unwrap_or(current_slot))
533 } else {
534 let epoch_start_slot = epoch_info.absolute_slot - epoch_info.slot_index;
535 (epoch_start_slot, current_slot)
536 }
537 } else {
538 let epoch_start_slot = epoch_info.absolute_slot - epoch_info.slot_index;
539 (epoch_start_slot, current_slot)
540 };
541
542 RpcResponse {
543 context: RpcResponseContext::new(current_slot),
544 value: RpcBlockProduction {
545 // Empty HashMap - no validator block production data in simulation
546 by_identity: std::collections::HashMap::new(),
547 range: solana_client::rpc_response::RpcBlockProductionRange {
548 first_slot,
549 last_slot,
550 },
551 },
552 }
553 })
554 .map_err(Into::into)
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use solana_client::rpc_config::RpcBlockProductionConfigRange;
561 use solana_commitment_config::CommitmentLevel;
562 use solana_inflation::Inflation;
563
564 use super::*;
565 use crate::tests::helpers::TestSetup;
566
567 #[tokio::test(flavor = "multi_thread")]
568 async fn test_get_epoch_schedule() {
569 let setup = TestSetup::new(SurfpoolBankDataRpc);
570 let res = setup.rpc.get_epoch_schedule(Some(setup.context)).unwrap();
571
572 assert_eq!(res, EpochSchedule::default());
573 }
574
575 #[test]
576 fn test_get_block_production() {
577 let setup = TestSetup::new(SurfpoolBankDataRpc);
578
579 // test with no config
580 let result = setup
581 .rpc
582 .get_block_production(Some(setup.context.clone()), None)
583 .unwrap();
584
585 // verify empty results (simulation mode)
586 assert!(
587 result.value.by_identity.is_empty(),
588 "Should have no validators in simulation"
589 );
590 assert!(
591 result.value.range.first_slot <= result.value.range.last_slot,
592 "Valid slot range"
593 );
594
595 // test with custom range
596 let config = Some(RpcBlockProductionConfig {
597 identity: None,
598 range: Some(RpcBlockProductionConfigRange {
599 first_slot: 100,
600 last_slot: Some(200),
601 }),
602 commitment: None,
603 });
604
605 let result2 = setup
606 .rpc
607 .get_block_production(Some(setup.context), config)
608 .unwrap();
609
610 assert_eq!(result2.value.range.first_slot, 100);
611 assert_eq!(result2.value.range.last_slot, 200);
612 assert!(result2.value.by_identity.is_empty());
613 }
614
615 #[test]
616 fn test_get_slot_leaders() {
617 let setup = TestSetup::new(SurfpoolBankDataRpc);
618
619 // test with valid parameters
620 let result = setup
621 .rpc
622 .get_slot_leaders(Some(setup.context.clone()), 0, 10)
623 .unwrap();
624
625 assert_eq!(
626 result[0],
627 SURFPOOL_IDENTITY_PUBKEY.to_string(),
628 "Should only return one leader - itself"
629 );
630
631 // test with invalid limit
632 let err = setup
633 .rpc
634 .get_slot_leaders(Some(setup.context.clone()), 0, 6000)
635 .unwrap_err();
636
637 assert_eq!(
638 err.code,
639 jsonrpc_core::ErrorCode::InvalidParams,
640 "Should return InvalidParams error for limit > 5000"
641 );
642
643 let latest_slot = setup.context.svm_locker.get_latest_absolute_slot();
644
645 // test with start_slot >= latest_slot
646 let err = setup
647 .rpc
648 .get_slot_leaders(Some(setup.context), latest_slot + 100, 10)
649 .unwrap_err();
650
651 assert_eq!(
652 err.code,
653 jsonrpc_core::ErrorCode::InvalidParams,
654 "Should return InvalidParams error for start_slot >= latest_slot"
655 );
656 }
657
658 #[test]
659 fn test_get_inflation_rate() {
660 let setup = TestSetup::new(SurfpoolBankDataRpc);
661 let result = setup.rpc.get_inflation_rate(Some(setup.context));
662 assert!(result.is_ok())
663 }
664
665 #[test]
666 fn test_get_inflation_governor() {
667 let setup = TestSetup::new(SurfpoolBankDataRpc);
668
669 let result = setup
670 .rpc
671 .get_inflation_governor(Some(setup.context), None)
672 .unwrap();
673
674 assert_eq!(result, Inflation::default().into());
675 }
676
677 #[test]
678 fn test_get_minimum_balance_for_rent_exemption() {
679 let setup = TestSetup::new(SurfpoolBankDataRpc);
680 let rent = setup
681 .rpc
682 .get_minimum_balance_for_rent_exemption(Some(setup.context), 0, None)
683 .unwrap();
684
685 assert_eq!(rent, 890880)
686 }
687
688 #[tokio::test(flavor = "multi_thread")]
689 async fn test_get_slot_leader_basic() {
690 let setup = TestSetup::new(SurfpoolBankDataRpc);
691
692 let result = setup.rpc.get_slot_leader(Some(setup.context.clone()), None);
693
694 match result {
695 Ok(identity) => {
696 assert_eq!(identity, SURFPOOL_IDENTITY_PUBKEY.to_string());
697 println!("✅ Basic test passed");
698 }
699 Err(e) => {
700 panic!("❌ Test failed: {:?}", e);
701 }
702 }
703 }
704
705 #[tokio::test(flavor = "multi_thread")]
706 async fn test_get_slot_leader_with_config() {
707 let setup = TestSetup::new(SurfpoolBankDataRpc);
708
709 let config = RpcContextConfig {
710 commitment: Some(CommitmentConfig {
711 commitment: CommitmentLevel::Processed,
712 }),
713 min_context_slot: None,
714 };
715
716 let result = setup
717 .rpc
718 .get_slot_leader(Some(setup.context.clone()), Some(config));
719
720 match result {
721 Ok(identity) => {
722 assert_eq!(identity, SURFPOOL_IDENTITY_PUBKEY.to_string());
723 println!("✅ Config test passed");
724 }
725 Err(e) => {
726 panic!("❌ Test failed: {:?}", e);
727 }
728 }
729 }
730
731 #[tokio::test(flavor = "multi_thread")]
732 async fn test_get_slot_leader_min_context_slot_error() {
733 let setup = TestSetup::new(SurfpoolBankDataRpc);
734
735 let config = RpcContextConfig {
736 commitment: Some(CommitmentConfig {
737 commitment: CommitmentLevel::Finalized,
738 }),
739 min_context_slot: Some(999999), // high number that should fail
740 };
741
742 let result = setup
743 .rpc
744 .get_slot_leader(Some(setup.context.clone()), Some(config));
745
746 assert!(result.is_err());
747 println!("✅ MinContextSlot error test passed");
748 }
749}