Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"runtime/debug"
)

var tag = "v4.5.35"
var tag = "v4.5.36"

var commit = func() string {
if info, ok := debug.ReadBuildInfo(); ok {
Expand Down
35 changes: 31 additions & 4 deletions rollup/internal/controller/sender/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,29 @@ func NewSender(ctx context.Context, config *config.SenderConfig, signerConfig *c
return nil, fmt.Errorf("failed to create transaction signer, err: %w", err)
}

// Set pending nonce
nonce, err := client.PendingNonceAt(ctx, transactionSigner.GetAddr())
// Get maximum nonce from database
dbNonce, err := orm.NewPendingTransaction(db).GetMaxNonceBySenderAddress(ctx, transactionSigner.GetAddr().Hex())
if err != nil {
return nil, fmt.Errorf("failed to get pending nonce for address %s, err: %w", transactionSigner.GetAddr(), err)
return nil, fmt.Errorf("failed to get max nonce from database for address %s, err: %w", transactionSigner.GetAddr().Hex(), err)
}
transactionSigner.SetNonce(nonce)

// Get pending nonce from the client
pendingNonce, err := client.PendingNonceAt(ctx, transactionSigner.GetAddr())
if err != nil {
return nil, fmt.Errorf("failed to get pending nonce for address %s, err: %w", transactionSigner.GetAddr().Hex(), err)
}

// Take the maximum of both values
var finalNonce uint64
if pendingNonce > dbNonce {
finalNonce = pendingNonce
} else {
finalNonce = dbNonce
}

log.Info("nonce initialization", "address", transactionSigner.GetAddr().Hex(), "pendingNonce", pendingNonce, "dbNonce", dbNonce, "finalNonce", finalNonce)

transactionSigner.SetNonce(finalNonce)

sender := &Sender{
ctx: ctx,
Expand Down Expand Up @@ -612,6 +629,16 @@ func (s *Sender) checkPendingTransaction() {
}

if err := s.client.SendTransaction(s.ctx, newSignedTx); err != nil {
if strings.Contains(err.Error(), "nonce too low") {
// 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.
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(), "err", err)

if updateErr := s.pendingTransactionOrm.UpdateTransactionStatusByTxHash(s.ctx, originalTx.Hash(), types.TxStatusConfirmedFailed); updateErr != nil {
log.Error("failed to update status of original transaction to confirmed failed", "txHash", originalTx.Hash().Hex(), "nonce", originalTx.Nonce(), "from", s.transactionSigner.GetAddr().Hex(), "err", updateErr)
return
}
return
}
// SendTransaction failed, need to rollback the previous database changes
if rollbackErr := s.db.Transaction(func(tx *gorm.DB) error {
// Restore original transaction status back to pending
Expand Down
58 changes: 58 additions & 0 deletions rollup/internal/orm/orm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,61 @@ func TestPendingTransactionOrm(t *testing.T) {
err = pendingTransactionOrm.DeleteTransactionByTxHash(context.Background(), common.HexToHash("0x123"))
assert.Error(t, err) // Should return error for non-existent transaction
}

func TestPendingTransaction_GetMaxNonceBySenderAddress(t *testing.T) {
sqlDB, err := db.DB()
assert.NoError(t, err)
assert.NoError(t, migrate.ResetDB(sqlDB))

// When there are no transactions for this sender address, should return 0
maxNonce, err := pendingTransactionOrm.GetMaxNonceBySenderAddress(context.Background(), "0xdeadbeef")
assert.NoError(t, err)
assert.Equal(t, uint64(0), maxNonce)

// Insert two transactions with different nonces for the same sender address
senderMeta := &SenderMeta{
Name: "testName",
Service: "testService",
Address: common.HexToAddress("0xdeadbeef"),
Type: types.SenderTypeCommitBatch,
}

tx0 := gethTypes.NewTx(&gethTypes.DynamicFeeTx{
Nonce: 1,
To: &common.Address{},
Data: []byte{},
Gas: 21000,
AccessList: gethTypes.AccessList{},
Value: big.NewInt(0),
ChainID: big.NewInt(1),
GasTipCap: big.NewInt(0),
GasFeeCap: big.NewInt(1),
V: big.NewInt(0),
R: big.NewInt(0),
S: big.NewInt(0),
})
tx1 := gethTypes.NewTx(&gethTypes.DynamicFeeTx{
Nonce: 3,
To: &common.Address{},
Data: []byte{},
Gas: 22000,
AccessList: gethTypes.AccessList{},
Value: big.NewInt(0),
ChainID: big.NewInt(1),
GasTipCap: big.NewInt(1),
GasFeeCap: big.NewInt(2),
V: big.NewInt(0),
R: big.NewInt(0),
S: big.NewInt(0),
})

err = pendingTransactionOrm.InsertPendingTransaction(context.Background(), "test", senderMeta, tx0, 0)
assert.NoError(t, err)
err = pendingTransactionOrm.InsertPendingTransaction(context.Background(), "test", senderMeta, tx1, 0)
assert.NoError(t, err)

// Now the max nonce for this sender should be 3
maxNonce, err = pendingTransactionOrm.GetMaxNonceBySenderAddress(context.Background(), senderMeta.Address.String())
assert.NoError(t, err)
assert.Equal(t, uint64(3), maxNonce)
}
17 changes: 17 additions & 0 deletions rollup/internal/orm/pending_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,20 @@ func (o *PendingTransaction) UpdateOtherTransactionsAsFailedByNonce(ctx context.
}
return nil
}

// GetMaxNonceBySenderAddress retrieves the maximum nonce for a specific sender address.
// Returns 0 if no transactions are found for the given address.
func (o *PendingTransaction) GetMaxNonceBySenderAddress(ctx context.Context, senderAddress string) (uint64, error) {
db := o.db.WithContext(ctx)
db = db.Model(&PendingTransaction{})
db = db.Where("sender_address = ?", senderAddress)
var maxNonce uint64
row := db.Model(&PendingTransaction{}).
Select("COALESCE(MAX(nonce), 0)").
Where("sender_address = ?", senderAddress).
Row()
if err := row.Scan(&maxNonce); err != nil {
return 0, fmt.Errorf("failed to get max nonce by sender address, address: %s, err: %w", senderAddress, err)
}
return maxNonce, nil
}