Skip to content

Commit ef55316

Browse files
frolchefsale
authored andcommitted
fix(jsonrpc): Fixed race condition in broadcast_tx_* methods
1 parent 6a5e79d commit ef55316

1 file changed

Lines changed: 54 additions & 2 deletions

File tree

chain/jsonrpc/src/lib.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)