diff --git a/banks-server/src/banks_server.rs b/banks-server/src/banks_server.rs index 0d6184f900..bf219fbf0a 100644 --- a/banks-server/src/banks_server.rs +++ b/banks-server/src/banks_server.rs @@ -167,6 +167,7 @@ impl Banks for BanksServer { serialize(&transaction).unwrap(), last_valid_block_height, None, + None, ); self.transaction_sender.send(info).unwrap(); } @@ -257,6 +258,7 @@ impl Banks for BanksServer { serialize(&transaction).unwrap(), last_valid_block_height, None, + None, ); self.transaction_sender.send(info).unwrap(); self.poll_signature_status(&signature, blockhash, last_valid_block_height, commitment) diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index cd9e2fa11d..93d78f47ca 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -21,6 +21,7 @@ pub struct RpcSendTransactionConfig { pub skip_preflight: bool, pub preflight_commitment: Option, pub encoding: Option, + pub max_retries: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 5702a270ea..deacb983dc 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -3246,6 +3246,8 @@ submission. - `skipPreflight: ` - if true, skip the preflight transaction checks (default: false) - `preflightCommitment: ` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) level to use for preflight (default: `"finalized"`). - `encoding: ` - (optional) Encoding used for the transaction data. Either `"base58"` (*slow*, **DEPRECATED**), or `"base64"`. (default: `"base58"`). + - `maxRetries: ` - (optional) Maximum number of times for the RPC node to retry sending the transaction to the leader. + If this parameter not provided, the RPC node will retry the transaction until it is finalized or until the blockhash expires. #### Results: diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 5a08973d46..cb19d5363e 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -2216,12 +2216,14 @@ fn _send_transaction( wire_transaction: Vec, last_valid_block_height: u64, durable_nonce_info: Option<(Pubkey, Hash)>, + max_retries: Option, ) -> Result { let transaction_info = TransactionInfo::new( signature, wire_transaction, last_valid_block_height, durable_nonce_info, + max_retries, ); meta.transaction_sender .lock() @@ -3291,6 +3293,7 @@ pub mod rpc_full { wire_transaction, last_valid_block_height, None, + None, ) } @@ -3390,6 +3393,7 @@ pub mod rpc_full { wire_transaction, last_valid_block_height, durable_nonce_info, + config.max_retries, ) } diff --git a/send-transaction-service/src/send_transaction_service.rs b/send-transaction-service/src/send_transaction_service.rs index 3cc826a754..468ee94486 100644 --- a/send-transaction-service/src/send_transaction_service.rs +++ b/send-transaction-service/src/send_transaction_service.rs @@ -28,6 +28,8 @@ pub struct TransactionInfo { pub wire_transaction: Vec, pub last_valid_block_height: u64, pub durable_nonce_info: Option<(Pubkey, Hash)>, + pub max_retries: Option, + retries: usize, } impl TransactionInfo { @@ -36,12 +38,15 @@ impl TransactionInfo { wire_transaction: Vec, last_valid_block_height: u64, durable_nonce_info: Option<(Pubkey, Hash)>, + max_retries: Option, ) -> Self { Self { signature, wire_transaction, last_valid_block_height, durable_nonce_info, + max_retries, + retries: 0, } } } @@ -51,6 +56,7 @@ struct ProcessTransactionsResult { rooted: u64, expired: u64, retried: u64, + max_retries_elapsed: u64, failed: u64, retained: u64, } @@ -173,7 +179,7 @@ impl SendTransactionService { ) -> ProcessTransactionsResult { let mut result = ProcessTransactionsResult::default(); - transactions.retain(|signature, transaction_info| { + transactions.retain(|signature, mut transaction_info| { if transaction_info.durable_nonce_info.is_some() { inc_new_counter_info!("send_transaction_service-nonced", 1); } @@ -200,6 +206,14 @@ impl SendTransactionService { inc_new_counter_info!("send_transaction_service-expired", 1); return false; } + if let Some(max_retries) = transaction_info.max_retries { + if transaction_info.retries >= max_retries { + info!("Dropping transaction due to max retries: {}", signature); + result.max_retries_elapsed += 1; + inc_new_counter_info!("send_transaction_service-max_retries", 1); + return false; + } + } match working_bank.get_signature_status_slot(signature) { None => { @@ -207,6 +221,7 @@ impl SendTransactionService { // dropped or landed in another fork. Re-send it info!("Retrying transaction: {}", signature); result.retried += 1; + transaction_info.retries += 1; inc_new_counter_info!("send_transaction_service-retry", 1); let addresses = leader_info .as_ref() @@ -339,6 +354,7 @@ mod test { vec![], root_bank.block_height() - 1, None, + None, ), ); let result = SendTransactionService::process_transactions::( @@ -362,7 +378,13 @@ mod test { info!("Rooted transactions are dropped..."); transactions.insert( rooted_signature, - TransactionInfo::new(rooted_signature, vec![], working_bank.block_height(), None), + TransactionInfo::new( + rooted_signature, + vec![], + working_bank.block_height(), + None, + None, + ), ); let result = SendTransactionService::process_transactions::( &working_bank, @@ -385,7 +407,13 @@ mod test { info!("Failed transactions are dropped..."); transactions.insert( failed_signature, - TransactionInfo::new(failed_signature, vec![], working_bank.block_height(), None), + TransactionInfo::new( + failed_signature, + vec![], + working_bank.block_height(), + None, + None, + ), ); let result = SendTransactionService::process_transactions::( &working_bank, @@ -413,6 +441,7 @@ mod test { vec![], working_bank.block_height(), None, + None, ), ); let result = SendTransactionService::process_transactions::( @@ -442,6 +471,7 @@ mod test { vec![], working_bank.block_height(), None, + None, ), ); let result = SendTransactionService::process_transactions::( @@ -461,6 +491,64 @@ mod test { ..ProcessTransactionsResult::default() } ); + transactions.clear(); + + info!("Transactions are only retried until max_retries"); + transactions.insert( + Signature::new(&[1; 64]), + TransactionInfo::new( + Signature::default(), + vec![], + working_bank.block_height(), + None, + Some(0), + ), + ); + transactions.insert( + Signature::new(&[2; 64]), + TransactionInfo::new( + Signature::default(), + vec![], + working_bank.block_height(), + None, + Some(1), + ), + ); + let result = SendTransactionService::process_transactions::( + &working_bank, + &root_bank, + &send_socket, + &tpu_address, + &mut transactions, + &None, + leader_forward_count, + ); + assert_eq!(transactions.len(), 1); + assert_eq!( + result, + ProcessTransactionsResult { + retried: 1, + max_retries_elapsed: 1, + ..ProcessTransactionsResult::default() + } + ); + let result = SendTransactionService::process_transactions::( + &working_bank, + &root_bank, + &send_socket, + &tpu_address, + &mut transactions, + &None, + leader_forward_count, + ); + assert!(transactions.is_empty()); + assert_eq!( + result, + ProcessTransactionsResult { + max_retries_elapsed: 1, + ..ProcessTransactionsResult::default() + } + ); } #[test] @@ -521,6 +609,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, durable_nonce)), + None, ), ); let result = SendTransactionService::process_transactions::( @@ -548,6 +637,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, Hash::new_unique())), + None, ), ); let result = SendTransactionService::process_transactions::( @@ -577,6 +667,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, Hash::new_unique())), + None, ), ); let result = SendTransactionService::process_transactions::( @@ -604,6 +695,7 @@ mod test { vec![], root_bank.block_height() - 1, Some((nonce_address, durable_nonce)), + None, ), ); let result = SendTransactionService::process_transactions::( @@ -632,6 +724,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, Hash::new_unique())), // runtime should advance nonce on failed transactions + None, ), ); let result = SendTransactionService::process_transactions::( @@ -660,6 +753,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, Hash::new_unique())), // runtime advances nonce when transaction lands + None, ), ); let result = SendTransactionService::process_transactions::( @@ -689,6 +783,7 @@ mod test { vec![], last_valid_block_height, Some((nonce_address, durable_nonce)), + None, ), ); let result = SendTransactionService::process_transactions::(