@@ -249,6 +249,39 @@ impl JsonRpcHandler {
249249 Ok ( Value :: String ( hash) )
250250 }
251251
252+ async fn tx_exists (
253+ & self ,
254+ tx_hash : CryptoHash ,
255+ signer_account_id : & AccountId ,
256+ ) -> Result < bool , ServerError > {
257+ timeout ( self . polling_config . polling_timeout , async {
258+ loop {
259+ // TODO(optimization): Introduce a view_client method to only get transaction
260+ // status without the information about execution outcomes.
261+ match self
262+ . view_client_addr
263+ . send ( TxStatus { tx_hash, signer_account_id : signer_account_id. clone ( ) } )
264+ . await
265+ {
266+ Ok ( Ok ( Some ( _) ) ) => {
267+ return Ok ( true ) ;
268+ }
269+ Ok ( Err ( TxStatusError :: MissingTransaction ( _) ) ) => {
270+ return Ok ( false ) ;
271+ }
272+ Err ( _) => return Err ( ServerError :: InternalError ) ,
273+ _ => { }
274+ }
275+ delay_for ( self . polling_config . polling_interval ) . await ;
276+ }
277+ } )
278+ . await
279+ . map_err ( |_| {
280+ near_metrics:: inc_counter ( & metrics:: RPC_TIMEOUT_TOTAL ) ;
281+ ServerError :: Timeout
282+ } ) ?
283+ }
284+
252285 async fn tx_status_fetch (
253286 & self ,
254287 tx_info : TransactionInfo ,
@@ -311,20 +344,39 @@ impl JsonRpcHandler {
311344 } ) ?
312345 }
313346
347+ /// Send a transaction idempotently (subsequent send of the same transaction will not cause
348+ /// any new side-effects and the result will be the same unless we garbage collected it
349+ /// already).
314350 async fn send_tx (
315351 & self ,
316352 tx : SignedTransaction ,
317353 check_only : bool ,
318354 ) -> Result < NetworkClientResponses , RpcError > {
319- Ok ( self
355+ let tx_hash = tx. get_hash ( ) ;
356+ let signer_account_id = tx. transaction . signer_id . clone ( ) ;
357+ let response = self
320358 . client_addr
321359 . send ( NetworkClientMessages :: Transaction {
322360 transaction : tx,
323361 is_forwarded : false ,
324362 check_only,
325363 } )
326364 . map_err ( |err| RpcError :: server_error ( Some ( ServerError :: from ( err) ) ) )
327- . await ?)
365+ . await ?;
366+
367+ // If we receive InvalidNonce error, it might be the case that the transaction was
368+ // resubmitted, and we should check if that is the case and return ValidTx response to
369+ // maintain idempotence of the send_tx method.
370+ if let NetworkClientResponses :: InvalidTx (
371+ near_primitives:: errors:: InvalidTxError :: InvalidNonce { .. } ,
372+ ) = response
373+ {
374+ if self . tx_exists ( tx_hash, & signer_account_id) . await ? {
375+ return Ok ( NetworkClientResponses :: ValidTx ) ;
376+ }
377+ }
378+
379+ Ok ( response)
328380 }
329381
330382 async fn send_tx_sync ( & self , params : Option < Value > ) -> Result < Value , RpcError > {
0 commit comments