Skip to content

Commit fa2401c

Browse files
authored
fix(sender): graceful restart (#1567)
Co-authored-by: colinlyguo <[email protected]>
1 parent 438a9fb commit fa2401c

File tree

4 files changed

+79
-8
lines changed

4 files changed

+79
-8
lines changed

common/version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"runtime/debug"
66
)
77

8-
var tag = "v4.4.78"
8+
var tag = "v4.4.79"
99

1010
var commit = func() string {
1111
if info, ok := debug.ReadBuildInfo(); ok {

rollup/internal/controller/sender/sender.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,12 @@ func (s *Sender) SendTransaction(contextID string, target *common.Address, data
232232
}
233233

234234
if err := s.client.SendTransaction(s.ctx, signedTx); err != nil {
235+
// Delete the transaction from the pending transaction table if it fails to send.
236+
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, signedTx.Hash()); updateErr != nil {
237+
log.Error("failed to delete transaction", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", updateErr)
238+
return common.Hash{}, fmt.Errorf("failed to delete transaction, err: %w", updateErr)
239+
}
240+
235241
log.Error("failed to send tx", "tx hash", signedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", signedTx.Nonce(), "err", err)
236242
// Check if contain nonce, and reset nonce
237243
// only reset nonce when it is not from resubmit
@@ -458,6 +464,15 @@ func (s *Sender) createReplacingTransaction(tx *gethTypes.Transaction, baseFee,
458464
blobGasFeeCap = maxBlobGasPrice
459465
}
460466

467+
// Check if any fee cap is less than double
468+
doubledTipCap := new(big.Int).Mul(originalGasTipCap, big.NewInt(2))
469+
doubledFeeCap := new(big.Int).Mul(originalGasFeeCap, big.NewInt(2))
470+
doubledBlobFeeCap := new(big.Int).Mul(originalBlobGasFeeCap, big.NewInt(2))
471+
if gasTipCap.Cmp(doubledTipCap) < 0 || gasFeeCap.Cmp(doubledFeeCap) < 0 || blobGasFeeCap.Cmp(doubledBlobFeeCap) < 0 {
472+
log.Error("gas fees must be at least double", "originalTipCap", originalGasTipCap, "currentTipCap", gasTipCap, "requiredTipCap", doubledTipCap, "originalFeeCap", originalGasFeeCap, "currentFeeCap", gasFeeCap, "requiredFeeCap", doubledFeeCap, "originalBlobFeeCap", originalBlobGasFeeCap, "currentBlobFeeCap", blobGasFeeCap, "requiredBlobFeeCap", doubledBlobFeeCap)
473+
return nil, errors.New("gas fees must be at least double")
474+
}
475+
461476
feeData.gasFeeCap = gasFeeCap
462477
feeData.gasTipCap = gasTipCap
463478
feeData.blobGasFeeCap = blobGasFeeCap
@@ -520,7 +535,7 @@ func (s *Sender) checkPendingTransaction() {
520535
if receipt.BlockNumber.Uint64() <= confirmed {
521536
if dbTxErr := s.db.Transaction(func(dbTX *gorm.DB) error {
522537
// Update the status of the transaction to TxStatusConfirmed.
523-
if updateErr := s.pendingTransactionOrm.UpdatePendingTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusConfirmed, dbTX); updateErr != nil {
538+
if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusConfirmed, dbTX); updateErr != nil {
524539
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)
525540
return updateErr
526541
}
@@ -595,7 +610,7 @@ func (s *Sender) checkPendingTransaction() {
595610
// 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.
596611
// This case will be handled by the checkPendingTransaction function.
597612
if dbTxErr := s.db.Transaction(func(dbTX *gorm.DB) error {
598-
if updateErr := s.pendingTransactionOrm.UpdatePendingTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusReplaced, dbTX); updateErr != nil {
613+
if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusReplaced, dbTX); updateErr != nil {
599614
return fmt.Errorf("failed to update status of transaction with hash %s to TxStatusReplaced, err: %w", newSignedTx.Hash().String(), updateErr)
600615
}
601616
if updateErr := s.pendingTransactionOrm.InsertPendingTransaction(s.ctx, txnToCheck.ContextID, s.getSenderMeta(), newSignedTx, blockNumber, dbTX); updateErr != nil {
@@ -608,6 +623,23 @@ func (s *Sender) checkPendingTransaction() {
608623
}
609624

610625
if err := s.client.SendTransaction(s.ctx, newSignedTx); err != nil {
626+
// SendTransaction failed, need to rollback the previous database changes
627+
if rollbackErr := s.db.Transaction(func(tx *gorm.DB) error {
628+
// Restore original transaction status back to pending
629+
if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusPending, tx); updateErr != nil {
630+
return fmt.Errorf("failed to rollback status of original transaction, err: %w", updateErr)
631+
}
632+
// Delete the new transaction that was inserted
633+
if updateErr := s.pendingTransactionOrm.DeleteTransactionByTxHash(s.ctx, newSignedTx.Hash(), tx); updateErr != nil {
634+
return fmt.Errorf("failed to delete new transaction, err: %w", updateErr)
635+
}
636+
return nil
637+
}); rollbackErr != nil {
638+
// Both SendTransaction and rollback failed
639+
log.Error("failed to rollback database after SendTransaction failed", "tx hash", newSignedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", newSignedTx.Nonce(), "sendTxErr", err, "rollbackErr", rollbackErr)
640+
return
641+
}
642+
611643
log.Error("failed to send replacing tx", "tx hash", newSignedTx.Hash().String(), "from", s.transactionSigner.GetAddr().String(), "nonce", newSignedTx.Nonce(), "err", err)
612644
return
613645
}

rollup/internal/orm/orm_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ func TestPendingTransactionOrm(t *testing.T) {
560560
err = pendingTransactionOrm.InsertPendingTransaction(context.Background(), "test", senderMeta, tx1, 0)
561561
assert.NoError(t, err)
562562

563-
err = pendingTransactionOrm.UpdatePendingTransactionStatusByTxHash(context.Background(), tx0.Hash(), types.TxStatusReplaced)
563+
err = pendingTransactionOrm.UpdateTransactionStatusByTxHash(context.Background(), tx0.Hash(), types.TxStatusReplaced)
564564
assert.NoError(t, err)
565565

566566
txs, err := pendingTransactionOrm.GetPendingOrReplacedTransactionsBySenderType(context.Background(), senderMeta.Type, 2)
@@ -577,7 +577,7 @@ func TestPendingTransactionOrm(t *testing.T) {
577577
assert.Equal(t, senderMeta.Address.String(), txs[1].SenderAddress)
578578
assert.Equal(t, senderMeta.Type, txs[1].SenderType)
579579

580-
err = pendingTransactionOrm.UpdatePendingTransactionStatusByTxHash(context.Background(), tx1.Hash(), types.TxStatusConfirmed)
580+
err = pendingTransactionOrm.UpdateTransactionStatusByTxHash(context.Background(), tx1.Hash(), types.TxStatusConfirmed)
581581
assert.NoError(t, err)
582582

583583
txs, err = pendingTransactionOrm.GetPendingOrReplacedTransactionsBySenderType(context.Background(), senderMeta.Type, 2)
@@ -594,4 +594,17 @@ func TestPendingTransactionOrm(t *testing.T) {
594594
status, err := pendingTransactionOrm.GetTxStatusByTxHash(context.Background(), tx0.Hash())
595595
assert.NoError(t, err)
596596
assert.Equal(t, types.TxStatusConfirmedFailed, status)
597+
598+
// Test DeleteTransactionByTxHash
599+
err = pendingTransactionOrm.DeleteTransactionByTxHash(context.Background(), tx0.Hash())
600+
assert.NoError(t, err)
601+
602+
// Verify the transaction is deleted
603+
status, err = pendingTransactionOrm.GetTxStatusByTxHash(context.Background(), tx0.Hash())
604+
assert.NoError(t, err)
605+
assert.Equal(t, types.TxStatusUnknown, status) // Should return unknown status for deleted transaction
606+
607+
// Try to delete non-existent transaction
608+
err = pendingTransactionOrm.DeleteTransactionByTxHash(context.Background(), common.HexToHash("0x123"))
609+
assert.Error(t, err) // Should return error for non-existent transaction
597610
}

rollup/internal/orm/pending_transaction.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/scroll-tech/go-ethereum/common"
1010
gethTypes "github.com/scroll-tech/go-ethereum/core/types"
11+
"github.com/scroll-tech/go-ethereum/log"
1112
"gorm.io/gorm"
1213

1314
"scroll-tech/common/types"
@@ -150,8 +151,33 @@ func (o *PendingTransaction) InsertPendingTransaction(ctx context.Context, conte
150151
return nil
151152
}
152153

153-
// UpdatePendingTransactionStatusByTxHash updates the status of a transaction based on the transaction hash.
154-
func (o *PendingTransaction) UpdatePendingTransactionStatusByTxHash(ctx context.Context, hash common.Hash, status types.TxStatus, dbTX ...*gorm.DB) error {
154+
// DeleteTransactionByTxHash permanently deletes a transaction record from the database by transaction hash.
155+
// Using permanent delete (Unscoped) instead of soft delete to prevent database bloat, as repeated SendTransaction failures
156+
// could write a large number of transactions to the database.
157+
func (o *PendingTransaction) DeleteTransactionByTxHash(ctx context.Context, hash common.Hash, dbTX ...*gorm.DB) error {
158+
db := o.db
159+
if len(dbTX) > 0 && dbTX[0] != nil {
160+
db = dbTX[0]
161+
}
162+
db = db.WithContext(ctx)
163+
db = db.Model(&PendingTransaction{})
164+
165+
// Perform permanent delete by using Unscoped()
166+
result := db.Where("hash = ?", hash.String()).Unscoped().Delete(&PendingTransaction{})
167+
if result.Error != nil {
168+
return fmt.Errorf("failed to delete transaction, err: %w", result.Error)
169+
}
170+
if result.RowsAffected == 0 {
171+
return fmt.Errorf("no transaction found with hash: %s", hash.String())
172+
}
173+
if result.RowsAffected > 0 {
174+
log.Warn("Successfully deleted transaction", "hash", hash.String())
175+
}
176+
return nil
177+
}
178+
179+
// UpdateTransactionStatusByTxHash updates the status of a transaction based on the transaction hash.
180+
func (o *PendingTransaction) UpdateTransactionStatusByTxHash(ctx context.Context, hash common.Hash, status types.TxStatus, dbTX ...*gorm.DB) error {
155181
db := o.db
156182
if len(dbTX) > 0 && dbTX[0] != nil {
157183
db = dbTX[0]
@@ -160,7 +186,7 @@ func (o *PendingTransaction) UpdatePendingTransactionStatusByTxHash(ctx context.
160186
db = db.Model(&PendingTransaction{})
161187
db = db.Where("hash = ?", hash.String())
162188
if err := db.Update("status", status).Error; err != nil {
163-
return fmt.Errorf("failed to UpdatePendingTransactionStatusByTxHash, txHash: %s, error: %w", hash, err)
189+
return fmt.Errorf("failed to UpdateTransactionStatusByTxHash, txHash: %s, error: %w", hash, err)
164190
}
165191
return nil
166192
}

0 commit comments

Comments
 (0)