Skip to content

Commit c563955

Browse files
feat: add should_retry function to manage retry logic, excluding specific errors such as ForesterNotEligible. (#1580)
1 parent 472feca commit c563955

File tree

1 file changed

+165
-144
lines changed

1 file changed

+165
-144
lines changed

sdk-libs/client/src/rpc/solana_rpc.rs

Lines changed: 165 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use solana_client::{
1111
rpc_client::RpcClient,
1212
rpc_config::{RpcSendTransactionConfig, RpcTransactionConfig},
1313
};
14-
use solana_program::{clock::Slot, hash::Hash, pubkey::Pubkey};
14+
use solana_program::{clock::Slot, hash::Hash, instruction::InstructionError, pubkey::Pubkey};
1515
use solana_sdk::{
1616
account::{Account, AccountSharedData},
1717
bs58,
@@ -20,7 +20,7 @@ use solana_sdk::{
2020
epoch_info::EpochInfo,
2121
instruction::Instruction,
2222
signature::{Keypair, Signature},
23-
transaction::Transaction,
23+
transaction::{Transaction, TransactionError},
2424
};
2525
use solana_transaction_status::{
2626
option_serializer::OptionSerializer, TransactionStatus, UiInstruction, UiTransactionEncoding,
@@ -124,6 +124,19 @@ impl SolanaRpcConnection {
124124
}
125125
}
126126

127+
async fn should_retry(&self, error: &RpcError) -> bool {
128+
match error {
129+
RpcError::TransactionError(TransactionError::InstructionError(
130+
_,
131+
InstructionError::Custom(6004),
132+
)) => {
133+
// Don't retry ForesterNotEligible error
134+
false
135+
}
136+
_ => true,
137+
}
138+
}
139+
127140
async fn retry<F, Fut, T>(&self, operation: F) -> Result<T, RpcError>
128141
where
129142
F: Fn() -> Fut,
@@ -139,6 +152,10 @@ impl SolanaRpcConnection {
139152
match operation().await {
140153
Ok(result) => return Ok(result),
141154
Err(e) => {
155+
if !self.should_retry(&e).await {
156+
return Err(e);
157+
}
158+
142159
attempts += 1;
143160
if attempts >= self.retry_config.max_retries
144161
|| start_time.elapsed() >= self.retry_config.timeout
@@ -170,6 +187,10 @@ impl SolanaRpcConnection {
170187
match operation().await {
171188
Ok(result) => return Ok(result),
172189
Err(e) => {
190+
if !self.should_retry(&e).await {
191+
return Err(e);
192+
}
193+
173194
attempts += 1;
174195
if attempts >= self.retry_config.max_retries
175196
|| start_time.elapsed() >= self.retry_config.timeout
@@ -269,14 +290,14 @@ impl RpcConnection for SolanaRpcConnection {
269290
self.rpc_rate_limiter = Some(rate_limiter);
270291
}
271292

272-
fn rpc_rate_limiter(&self) -> Option<&RateLimiter> {
273-
self.rpc_rate_limiter.as_ref()
274-
}
275-
276293
fn set_send_tx_rate_limiter(&mut self, rate_limiter: RateLimiter) {
277294
self.send_tx_rate_limiter = Some(rate_limiter);
278295
}
279296

297+
fn rpc_rate_limiter(&self) -> Option<&RateLimiter> {
298+
self.rpc_rate_limiter.as_ref()
299+
}
300+
280301
fn send_tx_rate_limiter(&self) -> Option<&RateLimiter> {
281302
self.send_tx_rate_limiter.as_ref()
282303
}
@@ -434,143 +455,6 @@ impl RpcConnection for SolanaRpcConnection {
434455
Ok(result)
435456
}
436457

437-
async fn create_and_send_transaction_with_public_event(
438-
&mut self,
439-
instructions: &[Instruction],
440-
payer: &Pubkey,
441-
signers: &[&Keypair],
442-
transaction_params: Option<TransactionParams>,
443-
) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
444-
let pre_balance = self.client.get_balance(payer)?;
445-
let latest_blockhash = self.client.get_latest_blockhash()?;
446-
447-
let mut instructions_vec = vec![
448-
solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(1_000_000),
449-
];
450-
instructions_vec.extend_from_slice(instructions);
451-
452-
let transaction = Transaction::new_signed_with_payer(
453-
instructions_vec.as_slice(),
454-
Some(payer),
455-
signers,
456-
latest_blockhash,
457-
);
458-
459-
let (signature, slot) = self
460-
.process_transaction_with_context(transaction.clone())
461-
.await?;
462-
463-
let mut vec = Vec::new();
464-
let mut vec_accounts = Vec::new();
465-
instructions_vec.iter().for_each(|x| {
466-
vec.push(x.data.clone());
467-
vec_accounts.push(x.accounts.iter().map(|x| x.pubkey).collect());
468-
});
469-
{
470-
let rpc_transaction_config = RpcTransactionConfig {
471-
encoding: Some(UiTransactionEncoding::Base64),
472-
commitment: Some(self.client.commitment()),
473-
..Default::default()
474-
};
475-
let transaction = self
476-
.client
477-
.get_transaction_with_config(&signature, rpc_transaction_config)
478-
.map_err(|e| RpcError::CustomError(e.to_string()))?;
479-
let decoded_transaction = transaction
480-
.transaction
481-
.transaction
482-
.decode()
483-
.clone()
484-
.unwrap();
485-
let account_keys = decoded_transaction.message.static_account_keys();
486-
let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
487-
RpcError::CustomError("Transaction missing metadata information".to_string())
488-
})?;
489-
if meta.status.is_err() {
490-
return Err(RpcError::CustomError(
491-
"Transaction status indicates an error".to_string(),
492-
));
493-
}
494-
495-
let inner_instructions = match &meta.inner_instructions {
496-
OptionSerializer::Some(i) => i,
497-
OptionSerializer::None => {
498-
return Err(RpcError::CustomError(
499-
"No inner instructions found".to_string(),
500-
));
501-
}
502-
OptionSerializer::Skip => {
503-
return Err(RpcError::CustomError(
504-
"No inner instructions found".to_string(),
505-
));
506-
}
507-
};
508-
509-
for ix in inner_instructions.iter() {
510-
for ui_instruction in ix.instructions.iter() {
511-
match ui_instruction {
512-
UiInstruction::Compiled(ui_compiled_instruction) => {
513-
let accounts = &ui_compiled_instruction.accounts;
514-
let data = bs58::decode(&ui_compiled_instruction.data)
515-
.into_vec()
516-
.map_err(|_| {
517-
RpcError::CustomError(
518-
"Failed to decode instruction data".to_string(),
519-
)
520-
})?;
521-
vec.push(data);
522-
vec_accounts.push(
523-
accounts
524-
.iter()
525-
.map(|x| account_keys[(*x) as usize])
526-
.collect(),
527-
);
528-
}
529-
UiInstruction::Parsed(_) => {
530-
println!("Parsed instructions are not implemented yet");
531-
}
532-
}
533-
}
534-
}
535-
}
536-
println!("vec: {:?}", vec);
537-
println!("vec_accounts {:?}", vec_accounts);
538-
let (parsed_event, _new_addresses) =
539-
event_from_light_transaction(vec.as_slice(), vec_accounts).unwrap();
540-
println!("event: {:?}", parsed_event);
541-
542-
if let Some(transaction_params) = transaction_params {
543-
let mut deduped_signers = signers.to_vec();
544-
deduped_signers.dedup();
545-
let post_balance = self.get_account(*payer).await?.unwrap().lamports;
546-
// a network_fee is charged if there are input compressed accounts or new addresses
547-
let mut network_fee: i64 = 0;
548-
if transaction_params.num_input_compressed_accounts != 0
549-
|| transaction_params.num_output_compressed_accounts != 0
550-
{
551-
network_fee += transaction_params.fee_config.network_fee as i64;
552-
}
553-
if transaction_params.num_new_addresses != 0 {
554-
network_fee += transaction_params.fee_config.address_network_fee as i64;
555-
}
556-
557-
let expected_post_balance = pre_balance as i64
558-
- i64::from(transaction_params.num_new_addresses)
559-
* transaction_params.fee_config.address_queue_rollover as i64
560-
- i64::from(transaction_params.num_output_compressed_accounts)
561-
* transaction_params.fee_config.state_merkle_tree_rollover as i64
562-
- transaction_params.compress
563-
- transaction_params.fee_config.solana_network_fee * deduped_signers.len() as i64
564-
- network_fee;
565-
566-
if post_balance as i64 != expected_post_balance {
567-
return Err(RpcError::AssertRpcError(format!("unexpected balance after transaction: expected {expected_post_balance}, got {post_balance}")));
568-
}
569-
}
570-
let event = parsed_event.map(|e| (e, signature, slot));
571-
Ok(event)
572-
}
573-
574458
async fn confirm_transaction(&self, signature: Signature) -> Result<bool, RpcError> {
575459
self.retry(|| async {
576460
self.client
@@ -690,6 +574,7 @@ impl RpcConnection for SolanaRpcConnection {
690574
})
691575
.await
692576
}
577+
693578
async fn get_transaction_slot(&mut self, signature: &Signature) -> Result<u64, RpcError> {
694579
self.retry(|| async {
695580
Ok(self
@@ -707,7 +592,6 @@ impl RpcConnection for SolanaRpcConnection {
707592
})
708593
.await
709594
}
710-
711595
async fn get_signature_statuses(
712596
&self,
713597
signatures: &[Signature],
@@ -717,10 +601,147 @@ impl RpcConnection for SolanaRpcConnection {
717601
.map(|response| response.value)
718602
.map_err(RpcError::from)
719603
}
604+
720605
async fn get_block_height(&mut self) -> Result<u64, RpcError> {
721606
self.retry(|| async { self.client.get_block_height().map_err(RpcError::from) })
722607
.await
723608
}
609+
async fn create_and_send_transaction_with_public_event(
610+
&mut self,
611+
instructions: &[Instruction],
612+
payer: &Pubkey,
613+
signers: &[&Keypair],
614+
transaction_params: Option<TransactionParams>,
615+
) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
616+
let pre_balance = self.client.get_balance(payer)?;
617+
let latest_blockhash = self.client.get_latest_blockhash()?;
618+
619+
let mut instructions_vec = vec![
620+
solana_sdk::compute_budget::ComputeBudgetInstruction::set_compute_unit_limit(1_000_000),
621+
];
622+
instructions_vec.extend_from_slice(instructions);
623+
624+
let transaction = Transaction::new_signed_with_payer(
625+
instructions_vec.as_slice(),
626+
Some(payer),
627+
signers,
628+
latest_blockhash,
629+
);
630+
631+
let (signature, slot) = self
632+
.process_transaction_with_context(transaction.clone())
633+
.await?;
634+
635+
let mut vec = Vec::new();
636+
let mut vec_accounts = Vec::new();
637+
instructions_vec.iter().for_each(|x| {
638+
vec.push(x.data.clone());
639+
vec_accounts.push(x.accounts.iter().map(|x| x.pubkey).collect());
640+
});
641+
{
642+
let rpc_transaction_config = RpcTransactionConfig {
643+
encoding: Some(UiTransactionEncoding::Base64),
644+
commitment: Some(self.client.commitment()),
645+
..Default::default()
646+
};
647+
let transaction = self
648+
.client
649+
.get_transaction_with_config(&signature, rpc_transaction_config)
650+
.map_err(|e| RpcError::CustomError(e.to_string()))?;
651+
let decoded_transaction = transaction
652+
.transaction
653+
.transaction
654+
.decode()
655+
.clone()
656+
.unwrap();
657+
let account_keys = decoded_transaction.message.static_account_keys();
658+
let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
659+
RpcError::CustomError("Transaction missing metadata information".to_string())
660+
})?;
661+
if meta.status.is_err() {
662+
return Err(RpcError::CustomError(
663+
"Transaction status indicates an error".to_string(),
664+
));
665+
}
666+
667+
let inner_instructions = match &meta.inner_instructions {
668+
OptionSerializer::Some(i) => i,
669+
OptionSerializer::None => {
670+
return Err(RpcError::CustomError(
671+
"No inner instructions found".to_string(),
672+
));
673+
}
674+
OptionSerializer::Skip => {
675+
return Err(RpcError::CustomError(
676+
"No inner instructions found".to_string(),
677+
));
678+
}
679+
};
680+
681+
for ix in inner_instructions.iter() {
682+
for ui_instruction in ix.instructions.iter() {
683+
match ui_instruction {
684+
UiInstruction::Compiled(ui_compiled_instruction) => {
685+
let accounts = &ui_compiled_instruction.accounts;
686+
let data = bs58::decode(&ui_compiled_instruction.data)
687+
.into_vec()
688+
.map_err(|_| {
689+
RpcError::CustomError(
690+
"Failed to decode instruction data".to_string(),
691+
)
692+
})?;
693+
vec.push(data);
694+
vec_accounts.push(
695+
accounts
696+
.iter()
697+
.map(|x| account_keys[(*x) as usize])
698+
.collect(),
699+
);
700+
}
701+
UiInstruction::Parsed(_) => {
702+
println!("Parsed instructions are not implemented yet");
703+
}
704+
}
705+
}
706+
}
707+
}
708+
println!("vec: {:?}", vec);
709+
println!("vec_accounts {:?}", vec_accounts);
710+
let (parsed_event, _new_addresses) =
711+
event_from_light_transaction(vec.as_slice(), vec_accounts).unwrap();
712+
println!("event: {:?}", parsed_event);
713+
714+
if let Some(transaction_params) = transaction_params {
715+
let mut deduped_signers = signers.to_vec();
716+
deduped_signers.dedup();
717+
let post_balance = self.get_account(*payer).await?.unwrap().lamports;
718+
// a network_fee is charged if there are input compressed accounts or new addresses
719+
let mut network_fee: i64 = 0;
720+
if transaction_params.num_input_compressed_accounts != 0
721+
|| transaction_params.num_output_compressed_accounts != 0
722+
{
723+
network_fee += transaction_params.fee_config.network_fee as i64;
724+
}
725+
if transaction_params.num_new_addresses != 0 {
726+
network_fee += transaction_params.fee_config.address_network_fee as i64;
727+
}
728+
729+
let expected_post_balance = pre_balance as i64
730+
- i64::from(transaction_params.num_new_addresses)
731+
* transaction_params.fee_config.address_queue_rollover as i64
732+
- i64::from(transaction_params.num_output_compressed_accounts)
733+
* transaction_params.fee_config.state_merkle_tree_rollover as i64
734+
- transaction_params.compress
735+
- transaction_params.fee_config.solana_network_fee * deduped_signers.len() as i64
736+
- network_fee;
737+
738+
if post_balance as i64 != expected_post_balance {
739+
return Err(RpcError::AssertRpcError(format!("unexpected balance after transaction: expected {expected_post_balance}, got {post_balance}")));
740+
}
741+
}
742+
let event = parsed_event.map(|e| (e, signature, slot));
743+
Ok(event)
744+
}
724745
}
725746

726747
impl MerkleTreeExt for SolanaRpcConnection {}

0 commit comments

Comments
 (0)