@@ -63,7 +63,7 @@ type FeeData struct {
6363 gasLimit uint64
6464}
6565
66- // Sender Transaction sender to send transaction to l1/l2 geth
66+ // Sender Transaction sender to send transaction to l1/l2
6767type Sender struct {
6868 config * config.SenderConfig
6969 gethClient * gethclient.Client
@@ -105,13 +105,7 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
105105 return nil , fmt .Errorf ("failed to create transaction signer, err: %w" , err )
106106 }
107107
108- // Set pending nonce
109- nonce , err := client .PendingNonceAt (ctx , transactionSigner .GetAddr ())
110- if err != nil {
111- return nil , fmt .Errorf ("failed to get pending nonce for address %s, err: %w" , transactionSigner .GetAddr (), err )
112- }
113- transactionSigner .SetNonce (nonce )
114-
108+ // Create sender instance first and then initialize nonce
115109 sender := & Sender {
116110 ctx : ctx ,
117111 config : config ,
@@ -127,8 +121,13 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
127121 service : service ,
128122 senderType : senderType ,
129123 }
130- sender .metrics = initSenderMetrics (reg )
131124
125+ // Initialize nonce using the new method
126+ if err := sender .resetNonce (); err != nil {
127+ return nil , fmt .Errorf ("failed to reset nonce: %w" , err )
128+ }
129+
130+ sender .metrics = initSenderMetrics (reg )
132131 go sender .loop (ctx )
133132
134133 return sender , nil
@@ -242,7 +241,10 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
242241 // Check if contain nonce, and reset nonce
243242 // only reset nonce when it is not from resubmit
244243 if strings .Contains (err .Error (), "nonce too low" ) {
245- s .resetNonce (context .Background ())
244+ if err := s .resetNonce (); err != nil {
245+ log .Warn ("failed to reset nonce after failed send transaction" , "address" , s .transactionSigner .GetAddr ().String (), "err" , err )
246+ return common.Hash {}, 0 , fmt .Errorf ("failed to reset nonce after failed send transaction, err: %w" , err )
247+ }
246248 }
247249 return common.Hash {}, 0 , fmt .Errorf ("failed to send transaction, err: %w" , err )
248250 }
@@ -327,14 +329,46 @@ func (s *Sender) createTx(feeData *FeeData, target *common.Address, data []byte,
327329 return signedTx , nil
328330}
329331
332+ // initializeNonce initializes the nonce by taking the maximum of database nonce and pending nonce.
333+ func (s * Sender ) initializeNonce () (uint64 , error ) {
334+ // Get maximum nonce from database
335+ dbNonce , err := s .pendingTransactionOrm .GetMaxNonceBySenderAddress (s .ctx , s .transactionSigner .GetAddr ().Hex ())
336+ if err != nil {
337+ return 0 , fmt .Errorf ("failed to get max nonce from database for address %s, err: %w" , s .transactionSigner .GetAddr ().Hex (), err )
338+ }
339+
340+ // Get pending nonce from the client
341+ pendingNonce , err := s .client .PendingNonceAt (s .ctx , s .transactionSigner .GetAddr ())
342+ if err != nil {
343+ return 0 , fmt .Errorf ("failed to get pending nonce for address %s, err: %w" , s .transactionSigner .GetAddr ().Hex (), err )
344+ }
345+
346+ // Take the maximum of pending nonce and (db nonce + 1)
347+ // Database stores the used nonce, so the next available nonce should be dbNonce + 1
348+ // When dbNonce is -1 (no records), dbNonce + 1 = 0, which is correct
349+ nextDbNonce := uint64 (dbNonce + 1 )
350+ var finalNonce uint64
351+ if pendingNonce > nextDbNonce {
352+ finalNonce = pendingNonce
353+ } else {
354+ finalNonce = nextDbNonce
355+ }
356+
357+ log .Info ("nonce initialization" , "address" , s .transactionSigner .GetAddr ().Hex (), "maxDbNonce" , dbNonce , "nextDbNonce" , nextDbNonce , "pendingNonce" , pendingNonce , "finalNonce" , finalNonce )
358+
359+ return finalNonce , nil
360+ }
361+
330362// resetNonce reset nonce if send signed tx failed.
331- func (s * Sender ) resetNonce (ctx context. Context ) {
332- nonce , err := s .client . PendingNonceAt ( ctx , s . transactionSigner . GetAddr () )
363+ func (s * Sender ) resetNonce () error {
364+ nonce , err := s .initializeNonce ( )
333365 if err != nil {
334- log .Warn ("failed to reset nonce" , "address" , s .transactionSigner .GetAddr ().String (), "err" , err )
335- return
366+ log .Error ("failed to reset nonce" , "address" , s .transactionSigner .GetAddr ().String (), "err" , err )
367+ return fmt . Errorf ( "failed to reset nonce, err: %w" , err )
336368 }
369+ log .Info ("reset nonce" , "address" , s .transactionSigner .GetAddr ().String (), "nonce" , nonce )
337370 s .transactionSigner .SetNonce (nonce )
371+ return nil
338372}
339373
340374func (s * Sender ) createReplacingTransaction (tx * gethTypes.Transaction , baseFee , blobBaseFee uint64 ) (* gethTypes.Transaction , error ) {
@@ -612,6 +646,16 @@ func (s *Sender) checkPendingTransaction() {
612646 }
613647
614648 if err := s .client .SendTransaction (s .ctx , newSignedTx ); err != nil {
649+ if strings .Contains (err .Error (), "nonce too low" ) {
650+ // When we receive a 'nonce too low' error but cannot find the transaction receipt, it indicates another transaction with this nonce has already been processed, so this transaction will never be mined and should be marked as failed.
651+ log .Warn ("nonce too low detected, marking all non-confirmed transactions with same nonce as failed" , "nonce" , originalTx .Nonce (), "address" , s .transactionSigner .GetAddr ().Hex (), "txHash" , originalTx .Hash ().Hex (), "newTxHash" , newSignedTx .Hash ().Hex (), "err" , err )
652+ txHashes := []string {originalTx .Hash ().Hex (), newSignedTx .Hash ().Hex ()}
653+ if updateErr := s .pendingTransactionOrm .UpdateTransactionStatusByTxHashes (s .ctx , txHashes , types .TxStatusConfirmedFailed ); updateErr != nil {
654+ log .Error ("failed to update transaction status" , "hashes" , txHashes , "err" , updateErr )
655+ return
656+ }
657+ return
658+ }
615659 // SendTransaction failed, need to rollback the previous database changes
616660 if rollbackErr := s .db .Transaction (func (tx * gorm.DB ) error {
617661 // Restore original transaction status back to pending
0 commit comments