Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SimulatedBackend: Transaction after rollback requires empty block commit #30842

Open
yohamta opened this issue Dec 2, 2024 · 0 comments
Open
Labels

Comments

@yohamta
Copy link

yohamta commented Dec 2, 2024

System information

Geth version: v1.14.12 (Gei Hinnom)
OS & Version: OSX

Expected behaviour

After rolling back a transaction in the simulated backend, subsequent transactions should be processed normally without requiring an empty block commit.

Actual behaviour

In v1.14.12, after rolling back a transaction, subsequent transactions require an empty block commit to be processed successfully. Without the empty block commit, the transaction receipt cannot be obtained. This behavior does not occur in v1.14.11, where transactions after rollback work normally.

Steps to reproduce the behaviour

  1. This issue is present in v1.14.12 but not in v1.14.11
  2. The test below demonstrates the issue. Run the test with go test:

Test code
ethclient/simulated/rollback_test.go

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)
	}
}

Output:

Running tool: /opt/homebrew/bin/go test -timeout 30s -run ^TestTransactionRollbackBehavior$ github.com/ethereum/go-ethereum/ethclient/simulated

--- FAIL: TestTransactionRollbackBehavior (2.04s)
    --- FAIL: TestTransactionRollbackBehavior/Case_2:_Transaction_After_Rollback_(Shows_Issue) (2.02s)
        /Users/geth-dev/go-ethereum/ethclient/simulated/issue_test.go:40: failed to get transaction receipt: not found
FAIL
FAIL	github.com/ethereum/go-ethereum/ethclient/simulated	2.438s
FAIL

Backtrace

N/A - This is a test behavior issue, not a crash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant