@@ -175,7 +175,6 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
175175 s .metrics .sendTransactionTotal .WithLabelValues (s .service , s .name ).Inc ()
176176 var (
177177 feeData * FeeData
178- tx * gethTypes.Transaction
179178 sidecar * gethTypes.BlobTxSidecar
180179 err error
181180 )
@@ -217,20 +216,35 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
217216 return common.Hash {}, fmt .Errorf ("failed to get fee data, err: %w" , err )
218217 }
219218
220- if tx , err = s .createAndSendTx (feeData , target , data , sidecar , nil ); err != nil {
219+ signedTx , err := s .createTx (feeData , target , data , sidecar , nil )
220+ if err != nil {
221221 s .metrics .sendTransactionFailureSendTx .WithLabelValues (s .service , s .name ).Inc ()
222- log .Error ("failed to create and send tx (non-resubmit case)" , "from" , s .transactionSigner .GetAddr ().String (), "nonce" , s .transactionSigner .GetNonce (), "err" , err )
223- return common.Hash {}, fmt .Errorf ("failed to create and send transaction, err: %w" , err )
222+ log .Error ("failed to create signed tx (non-resubmit case)" , "from" , s .transactionSigner .GetAddr ().String (), "nonce" , s .transactionSigner .GetNonce (), "err" , err )
223+ return common.Hash {}, fmt .Errorf ("failed to create signed transaction, err: %w" , err )
224224 }
225225
226- if err = s .pendingTransactionOrm .InsertPendingTransaction (s .ctx , contextID , s .getSenderMeta (), tx , blockNumber ); err != nil {
226+ // Insert the transaction into the pending transaction table.
227+ // A corner case is that the transaction is inserted into the table but not sent to the chain, because the server is stopped in the middle.
228+ // This case will be handled by the checkPendingTransaction function.
229+ if err = s .pendingTransactionOrm .InsertPendingTransaction (s .ctx , contextID , s .getSenderMeta (), signedTx , blockNumber ); err != nil {
227230 log .Error ("failed to insert transaction" , "from" , s .transactionSigner .GetAddr ().String (), "nonce" , s .transactionSigner .GetNonce (), "err" , err )
228231 return common.Hash {}, fmt .Errorf ("failed to insert transaction, err: %w" , err )
229232 }
230- return tx .Hash (), nil
233+
234+ if err := s .client .SendTransaction (s .ctx , signedTx ); err != nil {
235+ log .Error ("failed to send tx" , "tx hash" , signedTx .Hash ().String (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , signedTx .Nonce (), "err" , err )
236+ // Check if contain nonce, and reset nonce
237+ // only reset nonce when it is not from resubmit
238+ if strings .Contains (err .Error (), "nonce too low" ) {
239+ s .resetNonce (context .Background ())
240+ }
241+ return common.Hash {}, fmt .Errorf ("failed to send transaction, err: %w" , err )
242+ }
243+
244+ return signedTx .Hash (), nil
231245}
232246
233- func (s * Sender ) createAndSendTx (feeData * FeeData , target * common.Address , data []byte , sidecar * gethTypes.BlobTxSidecar , overrideNonce * uint64 ) (* gethTypes.Transaction , error ) {
247+ func (s * Sender ) createTx (feeData * FeeData , target * common.Address , data []byte , sidecar * gethTypes.BlobTxSidecar , overrideNonce * uint64 ) (* gethTypes.Transaction , error ) {
234248 var (
235249 nonce = s .transactionSigner .GetNonce ()
236250 txData gethTypes.TxData
@@ -292,14 +306,9 @@ func (s *Sender) createAndSendTx(feeData *FeeData, target *common.Address, data
292306 return nil , err
293307 }
294308
295- if err = s .client .SendTransaction (s .ctx , signedTx ); err != nil {
296- log .Error ("failed to send tx" , "tx hash" , signedTx .Hash ().String (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , signedTx .Nonce (), "err" , err )
297- // Check if contain nonce, and reset nonce
298- // only reset nonce when it is not from resubmit
299- if strings .Contains (err .Error (), "nonce too low" ) && overrideNonce == nil {
300- s .resetNonce (context .Background ())
301- }
302- return nil , err
309+ // update nonce when it is not from resubmit
310+ if overrideNonce == nil {
311+ s .transactionSigner .SetNonce (nonce + 1 )
303312 }
304313
305314 if feeData .gasTipCap != nil {
@@ -320,10 +329,6 @@ func (s *Sender) createAndSendTx(feeData *FeeData, target *common.Address, data
320329
321330 s .metrics .currentGasLimit .WithLabelValues (s .service , s .name ).Set (float64 (feeData .gasLimit ))
322331
323- // update nonce when it is not from resubmit
324- if overrideNonce == nil {
325- s .transactionSigner .SetNonce (nonce + 1 )
326- }
327332 return signedTx , nil
328333}
329334
@@ -337,7 +342,7 @@ func (s *Sender) resetNonce(ctx context.Context) {
337342 s .transactionSigner .SetNonce (nonce )
338343}
339344
340- func (s * Sender ) resubmitTransaction (tx * gethTypes.Transaction , baseFee , blobBaseFee uint64 ) (* gethTypes.Transaction , error ) {
345+ func (s * Sender ) createReplacingTransaction (tx * gethTypes.Transaction , baseFee , blobBaseFee uint64 ) (* gethTypes.Transaction , error ) {
341346 escalateMultipleNum := new (big.Int ).SetUint64 (s .config .EscalateMultipleNum )
342347 escalateMultipleDen := new (big.Int ).SetUint64 (s .config .EscalateMultipleDen )
343348 maxGasPrice := new (big.Int ).SetUint64 (s .config .MaxGasPrice )
@@ -468,12 +473,12 @@ func (s *Sender) resubmitTransaction(tx *gethTypes.Transaction, baseFee, blobBas
468473
469474 nonce := tx .Nonce ()
470475 s .metrics .resubmitTransactionTotal .WithLabelValues (s .service , s .name ).Inc ()
471- tx , err := s .createAndSendTx (& feeData , tx .To (), tx .Data (), tx .BlobTxSidecar (), & nonce )
476+ signedTx , err := s .createTx (& feeData , tx .To (), tx .Data (), tx .BlobTxSidecar (), & nonce )
472477 if err != nil {
473- log .Error ("failed to create and send tx (resubmit case)" , "from" , s .transactionSigner .GetAddr ().String (), "nonce" , nonce , "err" , err )
478+ log .Error ("failed to create signed tx (resubmit case)" , "from" , s .transactionSigner .GetAddr ().String (), "nonce" , nonce , "err" , err )
474479 return nil , err
475480 }
476- return tx , nil
481+ return signedTx , nil
477482}
478483
479484// checkPendingTransaction checks the confirmation status of pending transactions against the latest confirmed block number.
@@ -500,38 +505,37 @@ func (s *Sender) checkPendingTransaction() {
500505 }
501506
502507 for _ , txnToCheck := range transactionsToCheck {
503- tx := new (gethTypes.Transaction )
504- if err := tx .DecodeRLP (rlp .NewStream (bytes .NewReader (txnToCheck .RLPEncoding ), 0 )); err != nil {
508+ originalTx := new (gethTypes.Transaction )
509+ if err := originalTx .DecodeRLP (rlp .NewStream (bytes .NewReader (txnToCheck .RLPEncoding ), 0 )); err != nil {
505510 log .Error ("failed to decode RLP" , "context ID" , txnToCheck .ContextID , "sender meta" , s .getSenderMeta (), "err" , err )
506511 continue
507512 }
508513
509- receipt , err := s .client .TransactionReceipt (s .ctx , tx .Hash ())
514+ receipt , err := s .client .TransactionReceipt (s .ctx , originalTx .Hash ())
510515 if err == nil { // tx confirmed.
511516 if receipt .BlockNumber .Uint64 () <= confirmed {
512- err := s .db .Transaction (func (dbTX * gorm.DB ) error {
517+ if dbTxErr := s .db .Transaction (func (dbTX * gorm.DB ) error {
513518 // Update the status of the transaction to TxStatusConfirmed.
514- if err := s .pendingTransactionOrm .UpdatePendingTransactionStatusByTxHash (s .ctx , tx .Hash (), types .TxStatusConfirmed , dbTX ); err != nil {
515- log .Error ("failed to update transaction status by tx hash" , "hash" , tx .Hash ().String (), "sender meta" , s .getSenderMeta (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , tx .Nonce (), "err" , err )
516- return err
519+ if updateErr := s .pendingTransactionOrm .UpdatePendingTransactionStatusByTxHash (s .ctx , originalTx .Hash (), types .TxStatusConfirmed , dbTX ); updateErr != nil {
520+ log .Error ("failed to update transaction status by tx hash" , "hash" , originalTx .Hash ().String (), "sender meta" , s .getSenderMeta (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , originalTx .Nonce (), "err" , updateErr )
521+ return updateErr
517522 }
518523 // Update other transactions with the same nonce and sender address as failed.
519- if err := s .pendingTransactionOrm .UpdateOtherTransactionsAsFailedByNonce (s .ctx , txnToCheck .SenderAddress , tx .Nonce (), tx .Hash (), dbTX ); err != nil {
520- log .Error ("failed to update other transactions as failed by nonce" , "senderAddress" , txnToCheck .SenderAddress , "nonce" , tx .Nonce (), "excludedTxHash" , tx .Hash (), "err" , err )
521- return err
524+ if updateErr := s .pendingTransactionOrm .UpdateOtherTransactionsAsFailedByNonce (s .ctx , txnToCheck .SenderAddress , originalTx .Nonce (), originalTx .Hash (), dbTX ); updateErr != nil {
525+ log .Error ("failed to update other transactions as failed by nonce" , "senderAddress" , txnToCheck .SenderAddress , "nonce" , originalTx .Nonce (), "excludedTxHash" , originalTx .Hash (), "err" , updateErr )
526+ return updateErr
522527 }
523528 return nil
524- })
525- if err != nil {
526- log .Error ("db transaction failed after receiving confirmation" , "err" , err )
529+ }); dbTxErr != nil {
530+ log .Error ("db transaction failed after receiving confirmation" , "err" , dbTxErr )
527531 return
528532 }
529533
530534 // send confirm message
531535 s .confirmCh <- & Confirmation {
532536 ContextID : txnToCheck .ContextID ,
533537 IsSuccessful : receipt .Status == gethTypes .ReceiptStatusSuccessful ,
534- TxHash : tx .Hash (),
538+ TxHash : originalTx .Hash (),
535539 SenderType : s .senderType ,
536540 }
537541 }
@@ -548,52 +552,60 @@ func (s *Sender) checkPendingTransaction() {
548552
549553 // early return if the previous transaction has not been confirmed yet.
550554 // currentNonce is already the confirmed nonce + 1.
551- if tx .Nonce () > currentNonce {
552- log .Debug ("previous transaction not yet confirmed, skip bumping gas price" , "address" , txnToCheck .SenderAddress , "currentNonce" , currentNonce , "txNonce" , tx .Nonce ())
555+ if originalTx .Nonce () > currentNonce {
556+ log .Debug ("previous transaction not yet confirmed, skip bumping gas price" , "address" , txnToCheck .SenderAddress , "currentNonce" , currentNonce , "txNonce" , originalTx .Nonce ())
553557 continue
554558 }
555559
556560 // It's possible that the pending transaction was marked as failed earlier in this loop (e.g., if one of its replacements has already been confirmed).
557561 // Therefore, we fetch the current transaction status again for accuracy before proceeding.
558- status , err := s .pendingTransactionOrm .GetTxStatusByTxHash (s .ctx , tx .Hash ())
562+ status , err := s .pendingTransactionOrm .GetTxStatusByTxHash (s .ctx , originalTx .Hash ())
559563 if err != nil {
560- log .Error ("failed to get transaction status by tx hash" , "hash" , tx .Hash ().String (), "err" , err )
564+ log .Error ("failed to get transaction status by tx hash" , "hash" , originalTx .Hash ().String (), "err" , err )
561565 return
562566 }
563567 if status == types .TxStatusConfirmedFailed {
564- log .Warn ("transaction already marked as failed, skipping resubmission" , "hash" , tx .Hash ().String ())
568+ log .Warn ("transaction already marked as failed, skipping resubmission" , "hash" , originalTx .Hash ().String ())
565569 continue
566570 }
567571
568572 log .Info ("resubmit transaction" ,
569573 "service" , s .service ,
570574 "name" , s .name ,
571- "hash" , tx .Hash ().String (),
575+ "hash" , originalTx .Hash ().String (),
572576 "from" , s .transactionSigner .GetAddr ().String (),
573- "nonce" , tx .Nonce (),
577+ "nonce" , originalTx .Nonce (),
574578 "submitBlockNumber" , txnToCheck .SubmitBlockNumber ,
575579 "currentBlockNumber" , blockNumber ,
576580 "escalateBlocks" , s .config .EscalateBlocks )
577581
578- if newTx , err := s .resubmitTransaction (tx , baseFee , blobBaseFee ); err != nil {
582+ newSignedTx , err := s .createReplacingTransaction (originalTx , baseFee , blobBaseFee )
583+ if err != nil {
579584 s .metrics .resubmitTransactionFailedTotal .WithLabelValues (s .service , s .name ).Inc ()
580- log .Error ("failed to resubmit transaction" , "context ID" , txnToCheck .ContextID , "sender meta" , s .getSenderMeta (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , tx .Nonce (), "err" , err )
581- } else {
582- err := s .db .Transaction (func (dbTX * gorm.DB ) error {
583- // Update the status of the original transaction as replaced, while still checking its confirmation status.
584- if err := s .pendingTransactionOrm .UpdatePendingTransactionStatusByTxHash (s .ctx , tx .Hash (), types .TxStatusReplaced , dbTX ); err != nil {
585- return fmt .Errorf ("failed to update status of transaction with hash %s to TxStatusReplaced, err: %w" , tx .Hash ().String (), err )
586- }
587- // Record the new transaction that has replaced the original one.
588- if err := s .pendingTransactionOrm .InsertPendingTransaction (s .ctx , txnToCheck .ContextID , s .getSenderMeta (), newTx , blockNumber , dbTX ); err != nil {
589- return fmt .Errorf ("failed to insert new pending transaction with context ID: %s, nonce: %d, hash: %v, previous block number: %v, current block number: %v, err: %w" , txnToCheck .ContextID , newTx .Nonce (), newTx .Hash ().String (), txnToCheck .SubmitBlockNumber , blockNumber , err )
590- }
591- return nil
592- })
593- if err != nil {
594- log .Error ("db transaction failed after resubmitting" , "err" , err )
595- return
585+ log .Error ("failed to resubmit transaction" , "context ID" , txnToCheck .ContextID , "sender meta" , s .getSenderMeta (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , originalTx .Nonce (), "err" , err )
586+ return
587+ }
588+
589+ // Update the status of the original transaction as replaced, while still checking its confirmation status.
590+ // Insert the new transaction that has replaced the original one, and set the status as pending.
591+ // A corner case is that the transaction is inserted into the table but not sent to the chain, because the server is stopped in the middle.
592+ // This case will be handled by the checkPendingTransaction function.
593+ if dbTxErr := s .db .Transaction (func (dbTX * gorm.DB ) error {
594+ if updateErr := s .pendingTransactionOrm .UpdatePendingTransactionStatusByTxHash (s .ctx , originalTx .Hash (), types .TxStatusReplaced , dbTX ); updateErr != nil {
595+ return fmt .Errorf ("failed to update status of transaction with hash %s to TxStatusReplaced, err: %w" , newSignedTx .Hash ().String (), updateErr )
596+ }
597+ if updateErr := s .pendingTransactionOrm .InsertPendingTransaction (s .ctx , txnToCheck .ContextID , s .getSenderMeta (), newSignedTx , blockNumber , dbTX ); updateErr != nil {
598+ return fmt .Errorf ("failed to insert new pending transaction with context ID: %s, nonce: %d, hash: %v, previous block number: %v, current block number: %v, err: %w" , txnToCheck .ContextID , newSignedTx .Nonce (), newSignedTx .Hash ().String (), txnToCheck .SubmitBlockNumber , blockNumber , updateErr )
596599 }
600+ return nil
601+ }); dbTxErr != nil {
602+ log .Error ("db transaction failed after resubmitting" , "err" , dbTxErr )
603+ return
604+ }
605+
606+ if err := s .client .SendTransaction (s .ctx , newSignedTx ); err != nil {
607+ log .Error ("failed to send replacing tx" , "tx hash" , newSignedTx .Hash ().String (), "from" , s .transactionSigner .GetAddr ().String (), "nonce" , newSignedTx .Nonce (), "err" , err )
608+ return
597609 }
598610 }
599611 }
0 commit comments