From 978d392aa4a061a11a3e4e064ec4d1e0da402388 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Sun, 12 Jan 2025 22:24:14 +0800 Subject: [PATCH] core/txpool/legacypool, ethclient/simulated: ensure pending nonces are reset by subpool.Clear (TODO: add this for blobpool as well) --- core/txpool/legacypool/legacypool.go | 1 + ethclient/simulated/rollback_test.go | 107 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 ethclient/simulated/rollback_test.go diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 70be7034ee..fe8fa0cda1 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -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) diff --git a/ethclient/simulated/rollback_test.go b/ethclient/simulated/rollback_test.go new file mode 100644 index 0000000000..018f372391 --- /dev/null +++ b/ethclient/simulated/rollback_test.go @@ -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) + } +}