Skip to content

Commit

Permalink
core/txpool/legacypool, ethclient/simulated: ensure pending nonces ar…
Browse files Browse the repository at this point in the history
…e reset by subpool.Clear (TODO: add this for blobpool as well)
  • Loading branch information
jwasinger committed Jan 12, 2025
1 parent 5065e6c commit 978d392
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 0 deletions.
1 change: 1 addition & 0 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,7 @@ func (pool *LegacyPool) Clear() {
pool.priced = newPricedList(pool.all)
pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list)
pool.pendingNonces = newNoncer(pool.currentState)

if !pool.config.NoLocals && pool.config.Journal != "" {
pool.journal = newTxJournal(pool.config.Journal)
Expand Down
107 changes: 107 additions & 0 deletions ethclient/simulated/rollback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package simulated

import (
"context"
"testing"
"time"

"github.com/ethereum/go-ethereum/core/types"
)

// TestTransactionRollbackBehavior verifies the behavior of transactions
// in the simulated backend after rollback operations.
//
// The test demonstrates that after a rollback:
// 1. The first test shows normal transaction processing without rollback
// 2. The second test shows that transactions immediately after rollback fail
// 3. The third test shows a workaround: committing an empty block after rollback
// makes subsequent transactions succeed
func TestTransactionRollbackBehavior(t *testing.T) {
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()

t.Run("Case 1: Basic Transaction (Control Case)", func(t *testing.T) {
// Demonstrates normal transaction processing works as expected
tx := testSendSignedTx(t, sim)
sim.Commit()
assertSuccessfulReceipt(t, client, tx)
})

t.Run("Case 2: Transaction After Rollback (Shows Issue)", func(t *testing.T) {
// First transaction gets rolled back
_ = testSendSignedTx(t, sim)
sim.Rollback()

// Attempting to process a new transaction immediately after rollback
// Currently, this case fails to get a valid receipt
tx := testSendSignedTx(t, sim)
sim.Commit()
assertSuccessfulReceipt(t, client, tx)
})

t.Run("Case 3: Transaction After Rollback with Empty Block (Workaround)", func(t *testing.T) {
// First transaction gets rolled back
_ = testSendSignedTx(t, sim)
sim.Rollback()

// Workaround: Commit an empty block after rollback
sim.Commit()

// Now the new transaction succeeds
tx := testSendSignedTx(t, sim)
sim.Commit()
assertSuccessfulReceipt(t, client, tx)
})
}

// testSendSignedTx sends a signed transaction to the simulated backend.
// It does not commit the block.
func testSendSignedTx(t *testing.T, sim *Backend) *types.Transaction {
t.Helper()
client := sim.Client()
ctx := context.Background()

signedTx, err := newTx(sim, testKey)
if err != nil {
t.Fatalf("failed to create transaction: %v", err)
}

if err = client.SendTransaction(ctx, signedTx); err != nil {
t.Fatalf("failed to send transaction: %v", err)
}

return signedTx
}

// assertSuccessfulReceipt verifies that a transaction was successfully processed
// by checking its receipt status.
func assertSuccessfulReceipt(t *testing.T, client Client, tx *types.Transaction) {
t.Helper()
ctx := context.Background()

var (
receipt *types.Receipt
err error
)

// Poll for receipt with timeout
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
receipt, err = client.TransactionReceipt(ctx, tx.Hash())
if err == nil && receipt != nil {
break
}
time.Sleep(100 * time.Millisecond)
}

if err != nil {
t.Fatalf("failed to get transaction receipt: %v", err)
}
if receipt == nil {
t.Fatal("transaction receipt is nil")
}
if receipt.Status != types.ReceiptStatusSuccessful {
t.Fatalf("transaction failed with status: %v", receipt.Status)
}
}

0 comments on commit 978d392

Please sign in to comment.