Skip to content

Commit f7386ab

Browse files
authored
fix: use the config arg in the get_balance rpc method (#547)
1 parent d58df4c commit f7386ab

File tree

3 files changed

+228
-12
lines changed

3 files changed

+228
-12
lines changed

crates/core/src/rpc/full.rs

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,16 +2292,38 @@ impl Full for SurfpoolFullRpc {
22922292
&self,
22932293
meta: Self::Metadata,
22942294
encoded: String,
2295-
_config: Option<RpcContextConfig>, // TODO: use config
2295+
config: Option<RpcContextConfig>,
22962296
) -> Result<RpcResponse<Option<u64>>> {
22972297
let (_, message) =
22982298
decode_and_deserialize::<VersionedMessage>(encoded, TransactionBinaryEncoding::Base64)?;
22992299

2300-
meta.with_svm_reader(|svm_reader| RpcResponse {
2301-
context: RpcResponseContext::new(svm_reader.get_latest_absolute_slot()),
2300+
let RpcContextConfig {
2301+
commitment,
2302+
min_context_slot,
2303+
} = config.unwrap_or_default();
2304+
let min_ctx_slot = min_context_slot.unwrap_or_default();
2305+
2306+
let svm_locker = meta.get_svm_locker()?;
2307+
2308+
let slot = if let Some(commitment_config) = commitment {
2309+
svm_locker.get_slot_for_commitment(&commitment_config)
2310+
} else {
2311+
svm_locker.get_latest_absolute_slot()
2312+
};
2313+
2314+
if let Some(min_slot) = min_context_slot
2315+
&& slot < min_slot
2316+
{
2317+
return Err(RpcCustomError::MinContextSlotNotReached {
2318+
context_slot: min_ctx_slot,
2319+
}
2320+
.into());
2321+
}
2322+
2323+
Ok(RpcResponse {
2324+
context: RpcResponseContext::new(slot),
23022325
value: Some((message.header().num_required_signatures as u64) * 5000),
23032326
})
2304-
.map_err(Into::into)
23052327
}
23062328

23072329
fn get_stake_minimum_delegation(
@@ -2509,6 +2531,7 @@ mod tests {
25092531
use std::thread::JoinHandle;
25102532

25112533
use base64::{Engine, prelude::BASE64_STANDARD};
2534+
use bincode::Options;
25122535
use crossbeam_channel::Receiver;
25132536
use solana_account_decoder::{UiAccount, UiAccountData, UiAccountEncoding};
25142537
use solana_client::rpc_config::RpcSimulateTransactionAccountsConfig;
@@ -2521,7 +2544,10 @@ mod tests {
25212544
};
25222545
use solana_pubkey::Pubkey;
25232546
use solana_signer::Signer;
2524-
use solana_system_interface::{instruction as system_instruction, program as system_program};
2547+
use solana_system_interface::{
2548+
instruction::{self as system_instruction, transfer},
2549+
program as system_program,
2550+
};
25252551
use solana_transaction::{
25262552
Transaction,
25272553
versioned::{Legacy, TransactionVersion},
@@ -2645,6 +2671,115 @@ mod tests {
26452671
}
26462672
}
26472673

2674+
#[tokio::test(flavor = "multi_thread")]
2675+
async fn test_get_fee_for_message() {
2676+
let setup = TestSetup::new(SurfpoolFullRpc);
2677+
let runloop_context = setup.context;
2678+
let rpc_server = setup.rpc;
2679+
let payer = Keypair::new();
2680+
let recipient = Pubkey::new_unique();
2681+
let lamports_to_send = 5 * LAMPORTS_PER_SOL;
2682+
let commitment_config_to_use = CommitmentConfig::confirmed();
2683+
2684+
let wrong_comm_min_ctx_slot = runloop_context
2685+
.svm_locker
2686+
.get_slot_for_commitment(&commitment_config_to_use)
2687+
+ 10;
2688+
2689+
let wrong_min_slot = runloop_context.svm_locker.get_latest_absolute_slot() + 10;
2690+
let rpc_ctx_config_with_wrong_commitment = RpcContextConfig {
2691+
commitment: Some(commitment_config_to_use),
2692+
min_context_slot: Some(wrong_comm_min_ctx_slot),
2693+
};
2694+
let rpc_ctx_config_with_wrong_min_slot = RpcContextConfig {
2695+
commitment: None,
2696+
min_context_slot: Some(wrong_min_slot),
2697+
};
2698+
2699+
let instruction = transfer(&payer.pubkey(), &recipient, lamports_to_send);
2700+
2701+
let latest_blockhash = runloop_context
2702+
.svm_locker
2703+
.with_svm_reader(|svm| svm.latest_blockhash());
2704+
let message = solana_message::Message::new_with_blockhash(
2705+
&[instruction],
2706+
Some(&payer.pubkey()),
2707+
&latest_blockhash,
2708+
);
2709+
let num_required_signatures = message.header.num_required_signatures as u64;
2710+
let transaction =
2711+
VersionedTransaction::try_new(VersionedMessage::Legacy(message), &[&payer]).unwrap();
2712+
2713+
let message_bytes = bincode::options()
2714+
.with_fixint_encoding()
2715+
.serialize(&transaction.message)
2716+
.expect("message serialization");
2717+
let encoded_message = base64::engine::general_purpose::STANDARD.encode(&message_bytes);
2718+
2719+
let get_fee_with_correct_config_pass_result = rpc_server.get_fee_for_message(
2720+
Some(runloop_context.clone()),
2721+
encoded_message.clone(),
2722+
None,
2723+
);
2724+
2725+
assert!(
2726+
get_fee_with_correct_config_pass_result.is_ok(),
2727+
"Expected get_fee_for_message to pass with correct configs"
2728+
);
2729+
assert_eq!(
2730+
get_fee_with_correct_config_pass_result
2731+
.unwrap()
2732+
.value
2733+
.unwrap(),
2734+
(num_required_signatures as u64) * 5_000,
2735+
"Invalid return value"
2736+
);
2737+
2738+
let get_fee_with_wrong_commitment_fail_result = rpc_server.get_fee_for_message(
2739+
Some(runloop_context.clone()),
2740+
encoded_message.clone(),
2741+
Some(rpc_ctx_config_with_wrong_commitment),
2742+
);
2743+
2744+
let wrong_comm_expected_err: Result<()> = Result::Err(
2745+
RpcCustomError::MinContextSlotNotReached {
2746+
context_slot: wrong_comm_min_ctx_slot,
2747+
}
2748+
.into(),
2749+
);
2750+
2751+
assert!(
2752+
get_fee_with_wrong_commitment_fail_result.is_err(),
2753+
"expected this txn to fail when min_ctx_slot > slot_for_commitment"
2754+
);
2755+
2756+
assert_eq!(
2757+
get_fee_with_wrong_commitment_fail_result.err().unwrap(),
2758+
wrong_comm_expected_err.err().unwrap()
2759+
);
2760+
2761+
let get_fee_with_wrong_mint_slot_fail_result = rpc_server.get_fee_for_message(
2762+
Some(runloop_context.clone()),
2763+
encoded_message,
2764+
Some(rpc_ctx_config_with_wrong_min_slot),
2765+
);
2766+
2767+
let wrong_min_slot_expected_err: Result<()> = Result::Err(
2768+
RpcCustomError::MinContextSlotNotReached {
2769+
context_slot: wrong_min_slot,
2770+
}
2771+
.into(),
2772+
);
2773+
assert!(
2774+
get_fee_with_wrong_mint_slot_fail_result.is_err(),
2775+
"expected this txn to fail when min_ctx_slot > absolute_latest_slot"
2776+
);
2777+
assert_eq!(
2778+
get_fee_with_wrong_mint_slot_fail_result.err().unwrap(),
2779+
wrong_min_slot_expected_err.err().unwrap()
2780+
);
2781+
}
2782+
26482783
#[tokio::test(flavor = "multi_thread")]
26492784
async fn test_get_signature_statuses() {
26502785
let pks = (0..10).map(|_| Pubkey::new_unique());

crates/core/src/rpc/minimal.rs

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use solana_client::{
1212
},
1313
};
1414
use solana_clock::Slot;
15-
use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
15+
use solana_commitment_config::CommitmentLevel;
1616
use solana_epoch_info::EpochInfo;
1717
use solana_rpc_client_api::response::Response as RpcResponse;
1818

@@ -88,7 +88,7 @@ pub trait Minimal {
8888
&self,
8989
meta: Self::Metadata,
9090
pubkey_str: String,
91-
_config: Option<RpcContextConfig>,
91+
config: Option<RpcContextConfig>,
9292
) -> BoxFuture<Result<RpcResponse<u64>>>;
9393

9494
/// Returns information about the current epoch.
@@ -586,17 +586,21 @@ impl Minimal for SurfpoolMinimalRpc {
586586
&self,
587587
meta: Self::Metadata,
588588
pubkey_str: String,
589-
_config: Option<RpcContextConfig>, // TODO: use config
589+
config: Option<RpcContextConfig>,
590590
) -> BoxFuture<Result<RpcResponse<u64>>> {
591591
let pubkey = match verify_pubkey(&pubkey_str) {
592592
Ok(res) => res,
593593
Err(e) => return e.into(),
594594
};
595595

596+
let config = config.unwrap_or_default();
597+
let commitment_config = config.commitment.unwrap_or_default();
598+
let min_ctx_slot = config.min_context_slot;
599+
596600
let SurfnetRpcContext {
597601
svm_locker,
598602
remote_ctx,
599-
} = match meta.get_rpc_context(CommitmentConfig::confirmed()) {
603+
} = match meta.get_rpc_context(commitment_config) {
600604
Ok(res) => res,
601605
Err(e) => return e.into(),
602606
};
@@ -608,6 +612,15 @@ impl Minimal for SurfpoolMinimalRpc {
608612
..
609613
} = svm_locker.get_account(&remote_ctx, &pubkey, None).await?;
610614

615+
if let Some(min_slot) = min_ctx_slot
616+
&& slot < min_slot
617+
{
618+
return Err(RpcCustomError::MinContextSlotNotReached {
619+
context_slot: min_slot,
620+
}
621+
.into());
622+
}
623+
611624
let balance = match &account_update {
612625
GetAccountResult::FoundAccount(_, account, _)
613626
| GetAccountResult::FoundProgramAccount((_, account), _)
@@ -972,6 +985,72 @@ mod tests {
972985
assert_eq!(result.unwrap(), "ok");
973986
}
974987

988+
#[tokio::test(flavor = "multi_thread")]
989+
async fn test_get_balance() {
990+
let setup = TestSetup::new(SurfpoolMinimalRpc);
991+
992+
let airdrop_amount = 5 * 1_000_000_000u64;
993+
let to_airdrop_pubkey = Pubkey::new_unique();
994+
995+
setup
996+
.context
997+
.svm_locker
998+
.airdrop(&to_airdrop_pubkey, airdrop_amount)
999+
.unwrap()
1000+
.unwrap();
1001+
1002+
let pass_if_correct_config_result = setup
1003+
.rpc
1004+
.get_balance(
1005+
Some(setup.context.clone()),
1006+
to_airdrop_pubkey.to_string(),
1007+
None,
1008+
)
1009+
.await;
1010+
1011+
assert!(
1012+
pass_if_correct_config_result.is_ok(),
1013+
"Expected the operation to pass"
1014+
);
1015+
1016+
assert_eq!(
1017+
pass_if_correct_config_result.unwrap().value,
1018+
airdrop_amount,
1019+
"Invalid returned lamports for the account"
1020+
);
1021+
1022+
let wrong_min_slot = setup.context.svm_locker.get_latest_absolute_slot() + 100;
1023+
1024+
let fail_if_latest_slot_lt_min_ctx_slot_result = setup
1025+
.rpc
1026+
.get_balance(
1027+
Some(setup.context.clone()),
1028+
Pubkey::new_unique().to_string(),
1029+
Some(RpcContextConfig {
1030+
commitment: None,
1031+
min_context_slot: Some(wrong_min_slot),
1032+
}),
1033+
)
1034+
.await;
1035+
1036+
let expected_err: Result<()> = Result::Err(
1037+
RpcCustomError::MinContextSlotNotReached {
1038+
context_slot: wrong_min_slot,
1039+
}
1040+
.into(),
1041+
);
1042+
1043+
assert!(
1044+
fail_if_latest_slot_lt_min_ctx_slot_result.is_err(),
1045+
"Expected get_balance rpc method to fail when latest_absolute_slot < min_context_slot"
1046+
);
1047+
1048+
assert_eq!(
1049+
fail_if_latest_slot_lt_min_ctx_slot_result.err().unwrap(),
1050+
expected_err.err().unwrap()
1051+
);
1052+
}
1053+
9751054
#[test]
9761055
fn test_get_transaction_count() {
9771056
let setup = TestSetup::new(SurfpoolMinimalRpc);

crates/core/src/tests/integration.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{str::FromStr, sync::Arc, time::Duration};
22

33
use base64::Engine;
4+
use bincode::Options;
45
use crossbeam_channel::{unbounded, unbounded as crossbeam_unbounded};
56
use jsonrpc_core::{
67
Error, Result as JsonRpcResult,
@@ -11,7 +12,8 @@ use solana_account::Account;
1112
use solana_account_decoder::{UiAccountData, UiAccountEncoding, parse_account_data::ParsedAccount};
1213
use solana_address_lookup_table_interface::state::{AddressLookupTable, LookupTableMeta};
1314
use solana_client::{
14-
nonblocking::rpc_client::RpcClient, rpc_config::RpcSimulateTransactionConfig,
15+
nonblocking::rpc_client::RpcClient,
16+
rpc_config::{RpcContextConfig, RpcSimulateTransactionConfig},
1517
rpc_response::RpcLogsResponse,
1618
};
1719
use solana_clock::{Clock, Slot};
@@ -49,8 +51,8 @@ use crate::{
4951
error::SurfpoolError,
5052
rpc::{
5153
RunloopContext,
52-
full::FullClient,
53-
minimal::MinimalClient,
54+
full::{Full, FullClient, SurfpoolFullRpc},
55+
minimal::{Minimal, MinimalClient, SurfpoolMinimalRpc},
5456
surfnet_cheatcodes::{SurfnetCheatcodes, SurfnetCheatcodesRpc},
5557
},
5658
runloops::start_local_surfnet_runloop,

0 commit comments

Comments
 (0)