diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index fcf16b9cb..3fa2ac1b2 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -48,11 +48,13 @@ type testBlockChain struct { statedb *state.StateDB gasLimit uint64 chainHeadFeed *event.Feed + baseFee *big.Int } func (bc *testBlockChain) CurrentBlock() *types.Block { return types.NewBlock(&types.Header{ GasLimit: bc.gasLimit, + BaseFee: bc.baseFee, }, nil, nil, nil) } @@ -81,13 +83,23 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key data := make([]byte, bytes) rand.Read(data) - tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data, nil, nil), types.HomesteadSigner{}, key) + return tx +} + +func eip1559Transaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey, gasPremium, feeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, nil, nil, gasPremium, feeCap), types.HomesteadSigner{}, key) + return tx +} + +func malformedTransaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey, gasPrice, gasPremium, feeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasPrice, nil, gasPremium, feeCap), types.HomesteadSigner{}, key) return tx } func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 10000000, new(event.Feed), nil} key, _ := crypto.GenerateKey() pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -95,6 +107,26 @@ func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { return pool, key } +func setupEIP1559TxPool(baseFee *big.Int) (*TxPool, *ecdsa.PrivateKey) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), baseFee} + + key, _ := crypto.GenerateKey() + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + + return pool, key +} + +func setupEIP1559FinalizedTxPool(baseFee *big.Int) (*TxPool, *ecdsa.PrivateKey) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), baseFee} + + key, _ := crypto.GenerateKey() + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + + return pool, key +} + // validateTxPoolInternals checks various consistency invariants within the pool. func validateTxPoolInternals(pool *TxPool) error { pool.mu.RLock() @@ -195,7 +227,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { // setup pool with 2 transaction in it statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) - blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed)}, address, &trigger} + blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed), nil}, address, &trigger} tx0 := transaction(0, 100000, key) tx1 := transaction(1, 100000, key) @@ -229,6 +261,135 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { } } +func TestStateChangeDuringTransactionPoolResetEIP1559(t *testing.T) { + t.Parallel() + + var ( + key, _ = crypto.GenerateKey() + address = crypto.PubkeyToAddress(key.PublicKey) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + trigger = false + ) + + // setup pool with 2 transaction in it + statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) + blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed), big.NewInt(5)}, address, &trigger} + + tx0 := transaction(0, 100000, key) + tx1 := eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + nonce := pool.Nonce(address) + if nonce != 0 { + t.Fatalf("Invalid nonce, want 0, got %d", nonce) + } + + pool.AddRemotesSync([]*types.Transaction{tx0, tx1}) + + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } + + // trigger state change in the background + trigger = true + <-pool.requestReset(nil, nil) + + _, err := pool.Pending() + if err != nil { + t.Fatalf("Could not fetch pending transactions: %v", err) + } + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } +} + +func TestStateChangeDuringTransactionPoolResetEIP1559Finalized(t *testing.T) { + t.Parallel() + + var ( + key, _ = crypto.GenerateKey() + address = crypto.PubkeyToAddress(key.PublicKey) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + trigger = false + ) + + // setup pool with 2 transaction in it + statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) + blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed), big.NewInt(5)}, address, &trigger} + + tx0 := eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10)) + tx1 := eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + nonce := pool.Nonce(address) + if nonce != 0 { + t.Fatalf("Invalid nonce, want 0, got %d", nonce) + } + + pool.AddRemotesSync([]*types.Transaction{tx0, tx1}) + + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } + + // trigger state change in the background + trigger = true + <-pool.requestReset(nil, nil) + + _, err := pool.Pending() + if err != nil { + t.Fatalf("Could not fetch pending transactions: %v", err) + } + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } +} + +// Tests that error is thrown if BaseFee is not present after EIP1559 initialization +func TestTransactionPoolBaseFeeEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + key, _ := crypto.GenerateKey() + tx := transaction(1, 100000, key) + + if err := pool.AddRemote(tx); err != ErrNoBaseFee { + t.Error("expected", ErrNoBaseFee) + } +} + +func TestTransactionPoolBaseFeeEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + key, _ := crypto.GenerateKey() + tx := eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + + if err := pool.AddRemote(tx); err != ErrNoBaseFee { + t.Error("expected", ErrNoBaseFee) + } +} + func TestInvalidTransactions(t *testing.T) { t.Parallel() @@ -264,6 +425,135 @@ func TestInvalidTransactions(t *testing.T) { if err := pool.AddLocal(tx); err != nil { t.Error("expected", nil, "got", err) } + + tx = eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + pool.gasPrice = big.NewInt(1) + if err := pool.AddRemote(tx); err != ErrTxIsEIP1559 { + t.Error("expected", ErrTxIsEIP1559, "got", err) + } + if err := pool.AddLocal(tx); err != ErrTxIsEIP1559 { + t.Error("expected", ErrTxIsEIP1559, "got", err) + } +} + +func TestInvalidTransactionsEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + tx := eip1559Transaction(0, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ := deriveSender(tx) + + pool.currentState.AddBalance(from, big.NewInt(1)) + if err := pool.AddRemote(tx); err != ErrInsufficientFunds { + t.Error("expected", ErrInsufficientFunds) + } + + fakeBaseFee := big.NewInt(5) + eip1559GasPrice := new(big.Int).Add(fakeBaseFee, tx.GasPremium()) + + balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), eip1559GasPrice)) + pool.currentState.AddBalance(from, balance) + if err := pool.AddRemote(tx); err != ErrIntrinsicGas { + t.Error("expected", ErrIntrinsicGas, "got", err) + } + + pool.currentState.SetNonce(from, 1) + pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) + tx = eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10)) + if err := pool.AddRemote(tx); err != ErrNonceTooLow { + t.Error("expected", ErrNonceTooLow) + } + + tx = eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + pool.gasPrice = big.NewInt(1000) + if err := pool.AddRemote(tx); err != ErrUnderpriced { + t.Error("expected", ErrUnderpriced, "got", err) + } + if err := pool.AddLocal(tx); err != nil { + t.Error("expected", nil, "got", err) + } + + tx = transaction(2, 100000, key) + pool.gasPrice = big.NewInt(1) + if err := pool.AddRemote(tx); err != nil { + t.Error("expected", nil, "got", err) + } + if err := pool.AddLocal(tx); err.Error() != fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]) { + t.Error("expected", fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]), "got", err) + } + + tx = eip1559Transaction(2, 100000, key, big.NewInt(1), big.NewInt(10)) + if err := pool.AddRemote(tx); err != nil { + t.Error("expected", nil, "got", err) + } + if err := pool.AddLocal(tx); err.Error() != fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]) { + t.Error("expected", fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]), "got", err) + } + + if err := pool.AddLocal(tx); err.Error() != fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]) { + t.Error("expected", fmt.Sprintf("known transaction: %s", tx.Hash().String()[2:]), "got", err) + } + + tx = malformedTransaction(2, 100000, key, big.NewInt(5), big.NewInt(1), big.NewInt(10)) + if err := pool.AddRemote(tx); err != ErrTxSetsLegacyAndEIP1559Fields { + t.Error("expected", ErrTxSetsLegacyAndEIP1559Fields, "got", err) + } + + tx = malformedTransaction(2, 10000, key, nil, nil, nil) + if err := pool.AddRemote(tx); err != ErrMissingGasFields { + t.Error("expected", ErrMissingGasFields, "got", err) + } +} + +func TestInvalidTransactionsEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + tx := eip1559Transaction(0, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ := deriveSender(tx) + + pool.currentState.AddBalance(from, big.NewInt(1)) + if err := pool.AddRemote(tx); err != ErrInsufficientFunds { + t.Error("expected", ErrInsufficientFunds) + } + + fakeBaseFee := big.NewInt(5) + eip1559GasPrice := new(big.Int).Add(fakeBaseFee, tx.GasPremium()) + + balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), eip1559GasPrice)) + pool.currentState.AddBalance(from, balance) + if err := pool.AddRemote(tx); err != ErrIntrinsicGas { + t.Error("expected", ErrIntrinsicGas, "got", err) + } + + pool.currentState.SetNonce(from, 1) + pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) + tx = eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10)) + if err := pool.AddRemote(tx); err != ErrNonceTooLow { + t.Error("expected", ErrNonceTooLow) + } + + tx = eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + pool.gasPrice = big.NewInt(1000) + if err := pool.AddRemote(tx); err != ErrUnderpriced { + t.Error("expected", ErrUnderpriced, "got", err) + } + if err := pool.AddLocal(tx); err != nil { + t.Error("expected", nil, "got", err) + } + + tx = transaction(2, 100000, key) + pool.gasPrice = big.NewInt(1) + if err := pool.AddRemote(tx); err != ErrTxNotEIP1559 { + t.Error("expected", ErrTxNotEIP1559, "got", err) + } + if err := pool.AddLocal(tx); err != ErrTxNotEIP1559 { + t.Error("expected", ErrTxNotEIP1559, "got", err) + } } func TestTransactionQueue(t *testing.T) { @@ -323,6 +613,120 @@ func TestTransactionQueue2(t *testing.T) { } } +func TestTransactionQueueEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + tx := transaction(0, 100, key) + from, _ := deriveSender(tx) + pool.currentState.AddBalance(from, big.NewInt(1000)) + <-pool.requestReset(nil, nil) + + pool.enqueueTx(tx.Hash(), tx) + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if len(pool.pending) != 1 { + t.Error("expected valid txs to be 1 is", len(pool.pending)) + } + + tx = eip1559Transaction(1, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ = deriveSender(tx) + pool.currentState.SetNonce(from, 2) + pool.enqueueTx(tx.Hash(), tx) + + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { + t.Error("expected transaction to be in tx pool") + } + if len(pool.queue) > 0 { + t.Error("expected transaction queue to be empty. is", len(pool.queue)) + } +} + +func TestTransactionQueueEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + tx := eip1559Transaction(0, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ := deriveSender(tx) + pool.currentState.AddBalance(from, big.NewInt(1000)) + <-pool.requestReset(nil, nil) + + pool.enqueueTx(tx.Hash(), tx) + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if len(pool.pending) != 1 { + t.Error("expected valid txs to be 1 is", len(pool.pending)) + } + + tx = eip1559Transaction(1, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ = deriveSender(tx) + pool.currentState.SetNonce(from, 2) + pool.enqueueTx(tx.Hash(), tx) + + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { + t.Error("expected transaction to be in tx pool") + } + if len(pool.queue) > 0 { + t.Error("expected transaction queue to be empty. is", len(pool.queue)) + } +} + +func TestTransactionQueue2EIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + tx1 := transaction(0, 100, key) + tx2 := eip1559Transaction(10, 100, key, big.NewInt(1), big.NewInt(10)) + tx3 := transaction(11, 100, key) + from, _ := deriveSender(tx1) + pool.currentState.AddBalance(from, big.NewInt(1000)) + pool.reset(nil, nil) + + pool.enqueueTx(tx1.Hash(), tx1) + pool.enqueueTx(tx2.Hash(), tx2) + pool.enqueueTx(tx3.Hash(), tx3) + + pool.promoteExecutables([]common.Address{from}) + if len(pool.pending) != 1 { + t.Error("expected pending length to be 1, got", len(pool.pending)) + } + if pool.queue[from].Len() != 2 { + t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) + } +} + +func TestTransactionQueue2EIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + tx1 := eip1559Transaction(0, 100, key, big.NewInt(1), big.NewInt(10)) + tx2 := eip1559Transaction(10, 100, key, big.NewInt(1), big.NewInt(10)) + tx3 := eip1559Transaction(11, 100, key, big.NewInt(1), big.NewInt(10)) + from, _ := deriveSender(tx1) + pool.currentState.AddBalance(from, big.NewInt(1000)) + pool.reset(nil, nil) + + pool.enqueueTx(tx1.Hash(), tx1) + pool.enqueueTx(tx2.Hash(), tx2) + pool.enqueueTx(tx3.Hash(), tx3) + + pool.promoteExecutables([]common.Address{from}) + if len(pool.pending) != 1 { + t.Error("expected pending length to be 1, got", len(pool.pending)) + } + if pool.queue[from].Len() != 2 { + t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) + } +} + func TestTransactionNegativeValue(t *testing.T) { t.Parallel() @@ -337,6 +741,34 @@ func TestTransactionNegativeValue(t *testing.T) { } } +func TestTransactionNegativeValueEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, nil, nil, big.NewInt(1), big.NewInt(10)), types.HomesteadSigner{}, key) + from, _ := deriveSender(tx) + pool.currentState.AddBalance(from, big.NewInt(1)) + if err := pool.AddRemote(tx); err != ErrNegativeValue { + t.Error("expected", ErrNegativeValue, "got", err) + } +} + +func TestTransactionNegativeValueEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, nil, nil, big.NewInt(1), big.NewInt(10)), types.HomesteadSigner{}, key) + from, _ := deriveSender(tx) + pool.currentState.AddBalance(from, big.NewInt(1)) + if err := pool.AddRemote(tx); err != ErrNegativeValue { + t.Error("expected", ErrNegativeValue, "got", err) + } +} + func TestTransactionChainFork(t *testing.T) { t.Parallel() @@ -345,10 +777,10 @@ func TestTransactionChainFork(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), nil} <-pool.requestReset(nil, nil) } resetState() @@ -366,6 +798,64 @@ func TestTransactionChainFork(t *testing.T) { } } +func TestTransactionChainForkEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.AddBalance(addr, big.NewInt(100000000000000)) + + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(5)} + <-pool.requestReset(nil, nil) + } + resetState() + + tx := eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10)) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + pool.removeTx(tx.Hash(), true) + + // reset the pool's internal state + resetState() + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } +} + +func TestTransactionChainForkEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.AddBalance(addr, big.NewInt(100000000000000)) + + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(5)} + <-pool.requestReset(nil, nil) + } + resetState() + + tx := eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10)) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + pool.removeTx(tx.Hash(), true) + + // reset the pool's internal state + resetState() + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } +} + func TestTransactionDoubleNonce(t *testing.T) { t.Parallel() @@ -377,7 +867,7 @@ func TestTransactionDoubleNonce(t *testing.T) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), nil} <-pool.requestReset(nil, nil) } resetState() @@ -417,6 +907,153 @@ func TestTransactionDoubleNonce(t *testing.T) { } } +func TestTransactionDoubleNonceEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.AddBalance(addr, big.NewInt(100000000000000)) + + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(5)} + <-pool.requestReset(nil, nil) + } + resetState() + + signer := types.HomesteadSigner{} + tx1, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100000, big.NewInt(5), nil, nil, nil), signer, key) + tx2, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, nil, nil, big.NewInt(2), big.NewInt(10)), signer, key) + tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, nil, nil, big.NewInt(1), big.NewInt(10)), signer, key) + tx4, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(6), nil, nil, nil), signer, key) + tx5, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(8), nil, nil, nil), signer, key) + + // Add the first two transaction, ensure higher priced stays only + if replace, err := pool.add(tx1, false); err != nil || replace { + t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace) + } + if replace, err := pool.add(tx2, false); err != nil || !replace { + t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace) + } + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + + // Add the third transaction and ensure it's not saved (smaller EIP1559 price) + pool.add(tx3, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } + // Add the forth transaction and ensure it's not saved (smaller legacy price) + pool.add(tx4, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } + + // Add the fifth transaction and ensure it is saved (higher legacy price) + pool.add(tx5, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx5.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } +} + +func TestTransactionDoubleNonceEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.AddBalance(addr, big.NewInt(100000000000000)) + + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(5)} + <-pool.requestReset(nil, nil) + } + resetState() + + account := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(account, big.NewInt(1000)) + signer := types.HomesteadSigner{} + tx1, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100000, nil, nil, big.NewInt(1), big.NewInt(10)), signer, key) + tx2, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, nil, nil, big.NewInt(2), big.NewInt(10)), signer, key) + tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, nil, nil, big.NewInt(1), big.NewInt(10)), signer, key) + tx4, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, nil, nil, big.NewInt(3), big.NewInt(10)), signer, key) + + // Add the first two transaction, ensure higher priced stays only + if replace, err := pool.add(tx1, false); err != nil || replace { + t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace) + } + if replace, err := pool.add(tx2, false); err != nil || !replace { + t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace) + } + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + + // Add the third transaction and ensure it's not saved (smaller EIP1559 price) + pool.add(tx3, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } + // Add the forth transaction and ensure it's is saved (higher legacy price) + pool.add(tx4, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx4.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } +} + func TestTransactionMissingNonce(t *testing.T) { t.Parallel() @@ -440,6 +1077,65 @@ func TestTransactionMissingNonce(t *testing.T) { } } +func TestTransactionMissingNonceEIP1559(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(addr, big.NewInt(100000000000000)) + tx := transaction(1, 100000, key) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + if len(pool.pending) != 0 { + t.Error("expected 0 pending transactions, got", len(pool.pending)) + } + if pool.queue[addr].Len() != 1 { + t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + } + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } + tx = eip1559Transaction(2, 100000, key, big.NewInt(1), big.NewInt(10)) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + if len(pool.pending) != 0 { + t.Error("expected 0 pending transactions, got", len(pool.pending)) + } + if pool.queue[addr].Len() != 2 { + t.Error("expected 2 queued transaction, got", pool.queue[addr].Len()) + } + if pool.all.Count() != 2 { + t.Error("expected 2 total transactions, got", pool.all.Count()) + } +} + +func TestTransactionMissingNonceEIP1559Finalized(t *testing.T) { + t.Parallel() + + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(addr, big.NewInt(100000000000000)) + tx := eip1559Transaction(1, 100000, key, big.NewInt(1), big.NewInt(10)) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + if len(pool.pending) != 0 { + t.Error("expected 0 pending transactions, got", len(pool.pending)) + } + if pool.queue[addr].Len() != 1 { + t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + } + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } +} + func TestTransactionNonceRecovery(t *testing.T) { t.Parallel() @@ -464,6 +1160,54 @@ func TestTransactionNonceRecovery(t *testing.T) { } } +func TestTransactionNonceRecoveryEIP1559(t *testing.T) { + t.Parallel() + + const n = 10 + pool, key := setupEIP1559TxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.SetNonce(addr, n) + pool.currentState.AddBalance(addr, big.NewInt(100000000000000)) + <-pool.requestReset(nil, nil) + + tx := transaction(n, 100000, key) + if err := pool.AddRemote(tx); err != nil { + t.Error(err) + } + // simulate some weird re-order of transactions and missing nonce(s) + pool.currentState.SetNonce(addr, n-1) + <-pool.requestReset(nil, nil) + if fn := pool.Nonce(addr); fn != n-1 { + t.Errorf("expected nonce to be %d, got %d", n-1, fn) + } +} + +func TestTransactionNonceRecoveryEIP1559Finalized(t *testing.T) { + t.Parallel() + + const n = 10 + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(5)) + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.SetNonce(addr, n) + pool.currentState.AddBalance(addr, big.NewInt(100000000000000)) + <-pool.requestReset(nil, nil) + + tx := eip1559Transaction(n, 100000, key, big.NewInt(1), big.NewInt(10)) + if err := pool.AddRemote(tx); err != nil { + t.Error(err) + } + // simulate some weird re-order of transactions and missing nonce(s) + pool.currentState.SetNonce(addr, n-1) + <-pool.requestReset(nil, nil) + if fn := pool.Nonce(addr); fn != n-1 { + t.Errorf("expected nonce to be %d, got %d", n-1, fn) + } +} + // Tests that if an account runs out of funds, any pending and queued transactions // are dropped. func TestTransactionDropping(t *testing.T) { @@ -473,7 +1217,7 @@ func TestTransactionDropping(t *testing.T) { pool, key := setupTxPool() defer pool.Stop() - account := crypto.PubkeyToAddress(key.PublicKey) + account, _ := deriveSender(transaction(0, 0, key)) pool.currentState.AddBalance(account, big.NewInt(1000)) // Add some pending and some queued transactions @@ -520,19 +1264,203 @@ func TestTransactionDropping(t *testing.T) { t.Errorf("funded pending transaction missing: %v", tx0) } if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { - t.Errorf("funded pending transaction missing: %v", tx0) + t.Errorf("funded pending transaction missing: %v", tx1) } if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { - t.Errorf("out-of-fund pending transaction present: %v", tx1) + t.Errorf("out-of-fund pending transaction present: %v", tx2) } if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { - t.Errorf("funded queued transaction missing: %v", tx10) + t.Errorf("funded queued transaction missing: %v", tx11) } if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { - t.Errorf("out-of-fund queued transaction present: %v", tx11) + t.Errorf("out-of-fund queued transaction present: %v", tx12) + } + if pool.all.Count() != 4 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) + } + // Reduce the block gas limit, check that invalidated transactions are dropped + pool.chain.(*testBlockChain).gasLimit = 100 + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { + t.Errorf("over-gased pending transaction present: %v", tx1) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { + t.Errorf("over-gased queued transaction present: %v", tx11) + } + if pool.all.Count() != 2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2) + } +} + +func TestTransactionDroppingEIP1559(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559TxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000)) + + // Add some pending and some queued transactions + var ( + tx0 = transaction(0, 100, key) // cost = 200 + tx1 = transaction(1, 200, key) // cost = 300 + tx2 = eip1559Transaction(2, 300, key, big.NewInt(1), big.NewInt(10)) // cost = 700 + tx10 = transaction(10, 100, key) // cost = 200 + tx11 = eip1559Transaction(11, 200, key, big.NewInt(1), big.NewInt(10)) // cost = 500 + tx12 = eip1559Transaction(12, 300, key, big.NewInt(1), big.NewInt(10)) // cost = 700 + ) + pool.promoteTx(account, tx0.Hash(), tx0) + pool.promoteTx(account, tx1.Hash(), tx1) + pool.promoteTx(account, tx2.Hash(), tx2) + pool.enqueueTx(tx10.Hash(), tx10) + pool.enqueueTx(tx11.Hash(), tx11) + pool.enqueueTx(tx12.Hash(), tx12) + + // Check that pre and post validations leave the pool as is + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + <-pool.requestReset(nil, nil) + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + // Reduce the balance of the account, and check that invalidated transactions are dropped + pool.currentState.AddBalance(account, big.NewInt(-500)) + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx1) + } + if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { + t.Errorf("out-of-fund pending transaction present: %v", tx2) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx11) + } + if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { + t.Errorf("out-of-fund queued transaction present: %v", tx12) + } + if pool.all.Count() != 4 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) + } + // Reduce the block gas limit, check that invalidated transactions are dropped + pool.chain.(*testBlockChain).gasLimit = 100 + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { + t.Errorf("over-gased pending transaction present: %v", tx1) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { + t.Errorf("over-gased queued transaction present: %v", tx11) + } + if pool.all.Count() != 2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2) + } +} + +func TestTransactionDroppingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000)) + + // Add some pending and some queued transactions + var ( + tx0 = eip1559Transaction(0, 100, key, big.NewInt(1), big.NewInt(10)) // cost = 300 + tx1 = eip1559Transaction(1, 200, key, big.NewInt(1), big.NewInt(10)) // cost = 500 + tx2 = eip1559Transaction(2, 300, key, big.NewInt(1), big.NewInt(10)) // cost = 700 + tx10 = eip1559Transaction(10, 100, key, big.NewInt(1), big.NewInt(10)) // cost = 300 + tx11 = eip1559Transaction(11, 200, key, big.NewInt(1), big.NewInt(10)) // cost = 500 + tx12 = eip1559Transaction(12, 300, key, big.NewInt(1), big.NewInt(10)) // cost = 700 + ) + pool.promoteTx(account, tx0.Hash(), tx0) + pool.promoteTx(account, tx1.Hash(), tx1) + pool.promoteTx(account, tx2.Hash(), tx2) + pool.enqueueTx(tx10.Hash(), tx10) + pool.enqueueTx(tx11.Hash(), tx11) + pool.enqueueTx(tx12.Hash(), tx12) + + // Check that pre and post validations leave the pool as is + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + <-pool.requestReset(nil, nil) + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + // Reduce the balance of the account, and check that invalidated transactions are dropped + pool.currentState.AddBalance(account, big.NewInt(-500)) + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx1) + } + if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { + t.Errorf("out-of-fund pending transaction present: %v", tx2) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx11) + } + if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { + t.Errorf("out-of-fund queued transaction present: %v", tx12) } if pool.all.Count() != 4 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) @@ -565,8 +1493,8 @@ func TestTransactionPostponing(t *testing.T) { t.Parallel() // Create the pool to test the postponing with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -672,6 +1600,228 @@ func TestTransactionPostponing(t *testing.T) { } } +func TestTransactionPostponingEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the postponing with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create two test accounts to produce different gap profiles with + keys := make([]*ecdsa.PrivateKey, 2) + accs := make([]common.Address, len(keys)) + + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) + + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(100100)) + } + // Add a batch consecutive pending transactions for validation + txs := []*types.Transaction{} + for i, key := range keys { + + for j := 0; j < 100; j++ { + var tx *types.Transaction + if (i+j)%2 == 0 { + tx = eip1559Transaction(uint64(j), 25000, key, big.NewInt(1), big.NewInt(10)) + } else { + tx = transaction(uint64(j), 100000, key) + } + txs = append(txs, tx) + } + } + for i, err := range pool.AddRemotesSync(txs) { + if err != nil { + t.Fatalf("tx %d: failed to add transactions: %v", i, err) + } + } + // Check that pre and post validations leave the pool as is + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + <-pool.requestReset(nil, nil) + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + // Reduce the balance of the account, and check that transactions are reorganised + for _, addr := range accs { + pool.currentState.AddBalance(addr, big.NewInt(-1)) + } + <-pool.requestReset(nil, nil) + + // The first account's first transaction remains valid, check that subsequent + // ones are either filtered out, or queued up for later. + if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { + t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) + } + if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { + t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) + } + for i, tx := range txs[1:100] { + if i%2 == 1 { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) + } + } else { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) + } + } + } + // The second account's first transaction got invalid, check that all transactions + // are either filtered out, or queued up for later. + if pool.pending[accs[1]] != nil { + t.Errorf("invalidated account still has pending transactions") + } + for i, tx := range txs[100:] { + if i%2 == 1 { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) + } + } else { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) + } + } + } + if pool.all.Count() != len(txs)/2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2) + } +} + +func TestTransactionPostponingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the postponing with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create two test accounts to produce different gap profiles with + keys := make([]*ecdsa.PrivateKey, 2) + accs := make([]common.Address, len(keys)) + + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) + + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(100100)) + } + // Add a batch consecutive pending transactions for validation + txs := []*types.Transaction{} + for i, key := range keys { + + for j := 0; j < 100; j++ { + var tx *types.Transaction + if (i+j)%2 == 0 { + tx = eip1559Transaction(uint64(j), 25000, key, big.NewInt(1), big.NewInt(10)) + } else { + tx = eip1559Transaction(uint64(j), 50000, key, big.NewInt(1), big.NewInt(10)) + } + txs = append(txs, tx) + } + } + for i, err := range pool.AddRemotesSync(txs) { + if err != nil { + t.Fatalf("tx %d: failed to add transactions: %v", i, err) + } + } + // Check that pre and post validations leave the pool as is + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + <-pool.requestReset(nil, nil) + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + // Reduce the balance of the account, and check that transactions are reorganised + for _, addr := range accs { + pool.currentState.AddBalance(addr, big.NewInt(-1)) + } + <-pool.requestReset(nil, nil) + + // The first account's first transaction remains valid, check that subsequent + // ones are either filtered out, or queued up for later. + if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { + t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) + } + if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { + t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) + } + for i, tx := range txs[1:100] { + if i%2 == 1 { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) + } + } else { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) + } + } + } + // The second account's first transaction got invalid, check that all transactions + // are either filtered out, or queued up for later. + if pool.pending[accs[1]] != nil { + t.Errorf("invalidated account still has pending transactions") + } + for i, tx := range txs[100:] { + if i%2 == 1 { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) + } + } else { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) + } + } + } + if pool.all.Count() != len(txs)/2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2) + } +} + // Tests that if the transaction pool has both executable and non-executable // transactions from an origin account, filling the nonce gap moves all queued // ones into the pending pool. @@ -682,7 +1832,7 @@ func TestTransactionGapFilling(t *testing.T) { pool, key := setupTxPool() defer pool.Stop() - account := crypto.PubkeyToAddress(key.PublicKey) + account, _ := deriveSender(transaction(0, 0, key)) pool.currentState.AddBalance(account, big.NewInt(1000000)) // Keep track of transaction events to ensure all executables get announced @@ -727,6 +1877,110 @@ func TestTransactionGapFilling(t *testing.T) { } } +func TestTransactionGapFillingEIP1559(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559TxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a pending and a queued transaction with a nonce-gap in between + pool.AddRemotesSync([]*types.Transaction{ + transaction(0, 100000, key), + eip1559Transaction(2, 50000, key, big.NewInt(1), big.NewInt(10)), + }) + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Fill the nonce gap and ensure all transactions become pending + if err := pool.addRemoteSync(transaction(1, 100000, key)); err != nil { + t.Fatalf("failed to add gapped transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("gap-filling event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionGapFillingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a pending and a queued transaction with a nonce-gap in between + pool.AddRemotesSync([]*types.Transaction{ + eip1559Transaction(0, 50000, key, big.NewInt(1), big.NewInt(10)), + eip1559Transaction(2, 50000, key, big.NewInt(1), big.NewInt(10)), + }) + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Fill the nonce gap and ensure all transactions become pending + if err := pool.addRemoteSync(eip1559Transaction(1, 50000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add gapped transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("gap-filling event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that if the transaction count belonging to a single account goes above // some threshold, the higher transactions are dropped to prevent DOS attacks. func TestTransactionQueueAccountLimiting(t *testing.T) { @@ -736,7 +1990,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { pool, key := setupTxPool() defer pool.Stop() - account := crypto.PubkeyToAddress(key.PublicKey) + account, _ := deriveSender(transaction(0, 0, key)) pool.currentState.AddBalance(account, big.NewInt(1000000)) // Keep queuing up transactions and make sure all above a limit are dropped @@ -762,6 +2016,72 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { } } +func TestTransactionQueueAccountLimitingEIP1559(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559TxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(1); i <= testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(eip1559Transaction(i, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if len(pool.pending) != 0 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) + } + if i <= testTxPoolConfig.AccountQueue { + if pool.queue[account].Len() != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) + } + } else { + if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) + } + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) + } +} + +func TestTransactionQueueAccountLimitingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(1); i <= testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(eip1559Transaction(i, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if len(pool.pending) != 0 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) + } + if i <= testTxPoolConfig.AccountQueue { + if pool.queue[account].Len() != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) + } + } else { + if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) + } + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) + } +} + // Tests that if the transaction count belonging to multiple accounts go above // some threshold, the higher transactions are dropped to prevent DOS attacks. // @@ -778,8 +2098,8 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.NoLocals = nolocals @@ -851,18 +2171,182 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { } } +func TestTransactionQueueGlobalLimitingEIP1559(t *testing.T) { + testTransactionQueueGlobalLimitingEIP1559(t, false) +} +func TestTransactionQueueGlobalLimitingNoLocalsEIP1559(t *testing.T) { + testTransactionQueueGlobalLimitingEIP1559(t, true) +} + +func testTransactionQueueGlobalLimitingEIP1559(t *testing.T, nolocals bool) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.NoLocals = nolocals + config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible) + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them (last one will be the local) + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + local := keys[len(keys)-1] + + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := make(types.Transactions, 0, 3*config.GlobalQueue) + for len(txs) < cap(txs) { + key := keys[rand.Intn(len(keys)-1)] // skip adding transactions with the local account + addr := crypto.PubkeyToAddress(key.PublicKey) + + txs = append(txs, eip1559Transaction(nonces[addr]+1, 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) + + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + // Generate a batch of transactions from the local account and import them + txs = txs[:0] + for i := uint64(0); i < 3*config.GlobalQueue; i++ { + txs = append(txs, transaction(i+1, 100000, local)) + } + pool.AddLocals(txs) + + // If locals are disabled, the previous eviction algorithm should apply here too + if nolocals { + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + } else { + // Local exemptions are enabled, make sure the local account owned the queue + if len(pool.queue) != 1 { + t.Errorf("multiple accounts in queue: have %v, want %v", len(pool.queue), 1) + } + // Also ensure no local transactions are ever dropped, even if above global limits + if queued := pool.queue[crypto.PubkeyToAddress(local.PublicKey)].Len(); uint64(queued) != 3*config.GlobalQueue { + t.Fatalf("local account queued transaction count mismatch: have %v, want %v", queued, 3*config.GlobalQueue) + } + } +} + +func TestTransactionQueueGlobalLimitingEIP1559Finalized(t *testing.T) { + testTransactionQueueGlobalLimitingEIP1559Finalized(t, false) +} +func TestTransactionQueueGlobalLimitingNoLocalsEIP1559Finalized(t *testing.T) { + testTransactionQueueGlobalLimitingEIP1559Finalized(t, true) +} + +func testTransactionQueueGlobalLimitingEIP1559Finalized(t *testing.T, nolocals bool) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.NoLocals = nolocals + config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible) + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them (last one will be the local) + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + local := keys[len(keys)-1] + + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := make(types.Transactions, 0, 3*config.GlobalQueue) + for len(txs) < cap(txs) { + key := keys[rand.Intn(len(keys)-1)] // skip adding transactions with the local account + addr := crypto.PubkeyToAddress(key.PublicKey) + + txs = append(txs, eip1559Transaction(nonces[addr]+1, 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) + + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + // Generate a batch of transactions from the local account and import them + txs = txs[:0] + for i := uint64(0); i < 3*config.GlobalQueue; i++ { + txs = append(txs, eip1559Transaction(i+1, 100000, local, big.NewInt(1), big.NewInt(10))) + } + pool.AddLocals(txs) + + // If locals are disabled, the previous eviction algorithm should apply here too + if nolocals { + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + } else { + // Local exemptions are enabled, make sure the local account owned the queue + if len(pool.queue) != 1 { + t.Errorf("multiple accounts in queue: have %v, want %v", len(pool.queue), 1) + } + // Also ensure no local transactions are ever dropped, even if above global limits + if queued := pool.queue[crypto.PubkeyToAddress(local.PublicKey)].Len(); uint64(queued) != 3*config.GlobalQueue { + t.Fatalf("local account queued transaction count mismatch: have %v, want %v", queued, 3*config.GlobalQueue) + } + } +} + // Tests that if an account remains idle for a prolonged amount of time, any // non-executable transactions queued up are dropped to prevent wasting resources // on shuffling them around. // // This logic should not hold for local transactions, unless the local tracking // mechanism is disabled. -func TestTransactionQueueTimeLimiting(t *testing.T) { - testTransactionQueueTimeLimiting(t, false) -} -func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) { - testTransactionQueueTimeLimiting(t, true) -} +func TestTransactionQueueTimeLimiting(t *testing.T) { testTransactionQueueTimeLimiting(t, false) } +func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) { testTransactionQueueTimeLimiting(t, true) } func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { // Reduce the eviction interval to a testable amount @@ -870,8 +2354,8 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { evictionInterval = time.Second // Create the pool to test the non-expiration enforcement - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.Lifetime = time.Second @@ -925,6 +2409,142 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { } } +func TestTransactionQueueTimeLimitingEIP1559(t *testing.T) { + testTransactionQueueTimeLimitingEIP1559(t, false) +} +func TestTransactionQueueTimeLimitingNoLocalsEIP1559(t *testing.T) { + testTransactionQueueTimeLimitingEIP1559(t, true) +} + +func testTransactionQueueTimeLimitingEIP1559(t *testing.T, nolocals bool) { + // Reduce the eviction interval to a testable amount + defer func(old time.Duration) { evictionInterval = old }(evictionInterval) + evictionInterval = time.Second + + // Create the pool to test the non-expiration enforcement + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.Lifetime = time.Second + config.NoLocals = nolocals + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add the two transactions and ensure they both are queued up + if err := pool.AddLocal(eip1559Transaction(1, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains + time.Sleep(2 * config.Lifetime) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionQueueTimeLimitingEIP1559Finalized(t *testing.T) { + testTransactionQueueTimeLimitingEIP1559Finalized(t, false) +} +func TestTransactionQueueTimeLimitingNoLocalsEIP1559Finalized(t *testing.T) { + testTransactionQueueTimeLimitingEIP1559Finalized(t, true) +} + +func testTransactionQueueTimeLimitingEIP1559Finalized(t *testing.T, nolocals bool) { + // Reduce the eviction interval to a testable amount + defer func(old time.Duration) { evictionInterval = old }(evictionInterval) + evictionInterval = time.Second + + // Create the pool to test the non-expiration enforcement + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.Lifetime = time.Second + config.NoLocals = nolocals + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add the two transactions and ensure they both are queued up + if err := pool.AddLocal(eip1559Transaction(1, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(1, 100000, remote, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains + time.Sleep(2 * config.Lifetime) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that even if the transaction count belonging to a single account goes // above some threshold, as long as the transactions are executable, they are // accepted. @@ -966,6 +2586,82 @@ func TestTransactionPendingLimiting(t *testing.T) { } } +func TestTransactionPendingLimitingEIP1559(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559TxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(eip1559Transaction(i, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if pool.pending[account].Len() != int(i)+1 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) + } + if len(pool.queue) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) + } + if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { + t.Fatalf("event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionPendingLimitingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(1)) + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(eip1559Transaction(i, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if pool.pending[account].Len() != int(i)+1 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) + } + if len(pool.queue) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) + } + if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { + t.Fatalf("event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that if the transaction count belonging to multiple accounts go above // some hard threshold, the higher transactions are dropped to prevent DOS // attacks. @@ -973,8 +2669,8 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.GlobalSlots = config.AccountSlots * 10 @@ -1014,56 +2710,90 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) { } } -// Test the limit on transaction size is enforced correctly. -// This test verifies every transaction having allowed size -// is added to the pool, and longer transactions are rejected. -func TestTransactionAllowedTxSize(t *testing.T) { +func TestTransactionPendingGlobalLimitingEIP1559(t *testing.T) { t.Parallel() - // Create a test account and fund it - pool, key := setupTxPool() + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = config.AccountSlots * 10 + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) defer pool.Stop() - account := crypto.PubkeyToAddress(key.PublicKey) - pool.currentState.AddBalance(account, big.NewInt(1000000000)) + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) - // Compute maximal data size for transactions (lower bound). - // - // It is assumed the fields in the transaction (except of the data) are: - // - nonce <= 32 bytes - // - gasPrice <= 32 bytes - // - gasLimit <= 32 bytes - // - recipient == 20 bytes - // - value <= 32 bytes - // - signature == 65 bytes - // All those fields are summed up to at most 213 bytes. - baseSize := uint64(213) - dataSize := txMaxSize - baseSize + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.GlobalSlots)/len(keys)*2; j++ { + txs = append(txs, eip1559Transaction(nonces[addr], 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) - // Try adding a transaction with maximal allowed size - tx := pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(1), key, dataSize) - if err := pool.addRemoteSync(tx); err != nil { - t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) + pending := 0 + for _, list := range pool.pending { + pending += list.Len() } - // Try adding a transaction with random allowed size - if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentMaxGas, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { - t.Fatalf("failed to add transaction of random allowed size: %v", err) + if pending > int(config.GlobalSlots) { + t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) } - // Try adding a transaction of minimal not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, txMaxSize)); err == nil { - t.Fatalf("expected rejection on slightly oversize transaction") + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) } - // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { - t.Fatalf("expected rejection on oversize transaction") +} + +func TestTransactionPendingGlobalLimitingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = config.AccountSlots * 10 + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) } - // Run some sanity checks on the pool internals - pending, queued := pool.Stats() - if pending != 2 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.GlobalSlots)/len(keys)*2; j++ { + txs = append(txs, eip1559Transaction(nonces[addr], 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } } - if queued != 0 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) + + pending := 0 + for _, list := range pool.pending { + pending += list.Len() + } + if pending > int(config.GlobalSlots) { + t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) } if err := validateTxPoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) @@ -1075,8 +2805,8 @@ func TestTransactionCapClearsFromAll(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.AccountSlots = 2 @@ -1102,6 +2832,68 @@ func TestTransactionCapClearsFromAll(t *testing.T) { } } +func TestTransactionCapClearsFromAllEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.AccountSlots = 2 + config.AccountQueue = 2 + config.GlobalSlots = 8 + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(addr, big.NewInt(1000000)) + + txs := types.Transactions{} + for j := 0; j < int(config.GlobalSlots)*2; j++ { + txs = append(txs, eip1559Transaction(uint64(j), 100000, key, big.NewInt(1), big.NewInt(5))) + } + // Import the batch and verify that limits have been enforced + pool.AddRemotes(txs) + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionCapClearsFromAllEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.AccountSlots = 2 + config.AccountQueue = 2 + config.GlobalSlots = 8 + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(addr, big.NewInt(1000000)) + + txs := types.Transactions{} + for j := 0; j < int(config.GlobalSlots)*2; j++ { + txs = append(txs, eip1559Transaction(uint64(j), 100000, key, big.NewInt(1), big.NewInt(5))) + } + // Import the batch and verify that limits have been enforced + pool.AddRemotes(txs) + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that if the transaction count belonging to multiple accounts go above // some hard threshold, if they are under the minimum guaranteed slot count then // the transactions are still kept. @@ -1109,8 +2901,8 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.GlobalSlots = 1 @@ -1148,6 +2940,92 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) { } } +func TestTransactionPendingMinimumAllowanceEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.GlobalSlots = 1 + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.AccountSlots)*2; j++ { + txs = append(txs, eip1559Transaction(nonces[addr], 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) + + for addr, list := range pool.pending { + if list.Len() != int(config.AccountSlots) { + t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), config.AccountSlots) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionPendingMinimumAllowanceEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.GlobalSlots = 1 + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.AccountSlots)*2; j++ { + txs = append(txs, eip1559Transaction(nonces[addr], 100000, key, big.NewInt(1), big.NewInt(10))) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.AddRemotesSync(txs) + + for addr, list := range pool.pending { + if list.Len() != int(config.AccountSlots) { + t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), config.AccountSlots) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that setting the transaction pool gas price to a higher value correctly // discards everything cheaper than that and moves any gapped transactions back // from the pending pool to the queue. @@ -1157,8 +3035,8 @@ func TestTransactionPoolRepricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1272,14 +3150,252 @@ func TestTransactionPoolRepricing(t *testing.T) { } } +func TestTransactionPoolRepricingEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, eip1559Transaction(0, 100000, keys[0], big.NewInt(2), big.NewInt(10))) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(3), keys[0])) + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(2), keys[1])) + txs = append(txs, eip1559Transaction(1, 100000, keys[1], big.NewInt(2), big.NewInt(10))) + txs = append(txs, eip1559Transaction(2, 100000, keys[1], big.NewInt(2), big.NewInt(10))) + + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(3), keys[2])) + txs = append(txs, eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))) + txs = append(txs, pricedTransaction(3, 100000, big.NewInt(3), keys[2])) + + ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3]) + + // Import the batch and that both pending and queued transactions match up + pool.AddRemotesSync(txs) + pool.AddLocal(ltx) + + pending, queued := pool.Stats() + if pending != 7 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) + } + if queued != 3 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + } + if err := validateEvents(events, 7); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Reprice the pool and check that underpriced transactions get dropped + pool.SetGasPrice(big.NewInt(3)) + + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 5 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Check that we can't add the old transactions back + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))); err != ErrUnderpriced { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // However we can add local underpriced transactions + tx := pricedTransaction(1, 100000, big.NewInt(1), keys[3]) + if err := pool.AddLocal(tx); err != nil { + t.Fatalf("failed to add underpriced local transaction: %v", err) + } + if pending, _ = pool.Stats(); pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("post-reprice local event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // And we can fill gaps with properly priced transactions + if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(3), keys[0])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[2], big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to add queued transaction: %v", err) + } + if err := validateEvents(events, 5); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionPoolRepricingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, eip1559Transaction(0, 100000, keys[0], big.NewInt(2), big.NewInt(10))) + txs = append(txs, eip1559Transaction(1, 100000, keys[0], big.NewInt(3), big.NewInt(2))) // rejected because feeCap is lower than baseFee + gasPremium and lower than the gasPrice we set below + txs = append(txs, eip1559Transaction(2, 100000, keys[0], big.NewInt(2), big.NewInt(10))) + + txs = append(txs, eip1559Transaction(0, 100000, keys[1], big.NewInt(1), big.NewInt(10))) // rej + txs = append(txs, eip1559Transaction(1, 100000, keys[1], big.NewInt(2), big.NewInt(10))) + txs = append(txs, eip1559Transaction(2, 100000, keys[1], big.NewInt(2), big.NewInt(10))) + + txs = append(txs, eip1559Transaction(1, 100000, keys[2], big.NewInt(2), big.NewInt(10))) + txs = append(txs, eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))) // rej + txs = append(txs, eip1559Transaction(3, 100000, keys[2], big.NewInt(2), big.NewInt(10))) + + ltx := eip1559Transaction(0, 100000, keys[3], big.NewInt(1), big.NewInt(10)) + + // Import the batch and that both pending and queued transactions match up + pool.AddRemotesSync(txs) + pool.AddLocal(ltx) + + pending, queued := pool.Stats() + if pending != 7 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) + } + if queued != 3 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + } + if err := validateEvents(events, 7); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Reprice the pool and check that underpriced transactions get dropped + pool.SetGasPrice(big.NewInt(3)) + + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 5 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Check that we can't add the old transactions back + if err := pool.AddRemote(eip1559Transaction(1, 100000, keys[0], big.NewInt(3), big.NewInt(2))); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, keys[1], big.NewInt(1), big.NewInt(10))); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))); err != ErrUnderpriced { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // However we can add local underpriced transactions + tx := eip1559Transaction(1, 100000, keys[3], big.NewInt(1), big.NewInt(10)) + if err := pool.AddLocal(tx); err != nil { + t.Fatalf("failed to add underpriced local transaction: %v", err) + } + if pending, _ = pool.Stats(); pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("post-reprice local event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // And we can fill gaps with properly priced transactions + if err := pool.AddRemote(eip1559Transaction(1, 100000, keys[0], big.NewInt(3), big.NewInt(3))); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, keys[1], big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[2], big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to add queued transaction: %v", err) + } + if err := validateEvents(events, 5); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that setting the transaction pool gas price to a higher value does not // remove local transactions. func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1331,6 +3447,120 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { validate() } +func TestTransactionPoolRepricingKeepsLocalsEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000*1000000)) + } + // Create transaction (both pending and queued) with a linearly growing gasprice + for i := uint64(0); i < 500; i++ { + // Add pending transaction. + pendingTx := pricedTransaction(i, 100000, big.NewInt(int64(i+1)), keys[2]) + if err := pool.AddLocal(pendingTx); err != nil { + t.Fatal(err) + } + // Add queued transaction. + queuedTx := eip1559Transaction(i+501, 100000, keys[2], big.NewInt(int64(i)), big.NewInt(int64(i+9))) + if err := pool.AddLocal(queuedTx); err != nil { + t.Fatal(err) + } + } + pending, queued := pool.Stats() + expPending, expQueued := 500, 500 + validate := func() { + pending, queued = pool.Stats() + if pending != expPending { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, expPending) + } + if queued != expQueued { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) + } + + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + } + validate() + + // Reprice the pool and check that nothing is dropped + pool.SetGasPrice(big.NewInt(2)) + validate() + + pool.SetGasPrice(big.NewInt(2)) + pool.SetGasPrice(big.NewInt(4)) + pool.SetGasPrice(big.NewInt(8)) + pool.SetGasPrice(big.NewInt(100)) + validate() +} + +func TestTransactionPoolRepricingKeepsLocalsEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000*1000000)) + } + // Create transaction (both pending and queued) with a linearly growing gasprice + for i := uint64(0); i < 500; i++ { + // Add pending transaction. + pendingTx := eip1559Transaction(i, 100000, keys[2], big.NewInt(int64(i)), big.NewInt(int64(i+9))) + if err := pool.AddLocal(pendingTx); err != nil { + t.Fatal(err) + } + // Add queued transaction. + queuedTx := eip1559Transaction(i+501, 100000, keys[2], big.NewInt(int64(i)), big.NewInt(int64(i+9))) + if err := pool.AddLocal(queuedTx); err != nil { + t.Fatal(err) + } + } + pending, queued := pool.Stats() + expPending, expQueued := 500, 500 + validate := func() { + pending, queued = pool.Stats() + if pending != expPending { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, expPending) + } + if queued != expQueued { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) + } + + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + } + validate() + + // Reprice the pool and check that nothing is dropped + pool.SetGasPrice(big.NewInt(2)) + validate() + + pool.SetGasPrice(big.NewInt(2)) + pool.SetGasPrice(big.NewInt(4)) + pool.SetGasPrice(big.NewInt(8)) + pool.SetGasPrice(big.NewInt(100)) + validate() +} + // Tests that when the pool reaches its global transaction limit, underpriced // transactions are gradually shifted out for more expensive ones and any gapped // pending transactions are moved into the queue. @@ -1340,8 +3570,8 @@ func TestTransactionPoolUnderpricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()),nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.GlobalSlots = 2 @@ -1439,6 +3669,268 @@ func TestTransactionPoolUnderpricing(t *testing.T) { } } +func TestTransactionPoolUnderpricingEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = 2 + config.GlobalQueue = 2 + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[0])) + txs = append(txs, eip1559Transaction(1, 100000, keys[0], big.NewInt(1), big.NewInt(10))) + + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[1])) + + ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[2]) + + // Import the batch and that both pending and queued transactions match up + pool.AddRemotes(txs) + pool.AddLocal(ltx) + + pending, queued := pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 3); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding an underpriced transaction on block limit fails + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + // Ensure that adding high priced transactions drops cheap ones, but not own + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[1], big.NewInt(3), big.NewInt(10))); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding local transactions can push out even higher priced ones + ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = eip1559Transaction(0, 100000, keys[3], big.NewInt(0), big.NewInt(0)) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("local event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionPoolUnderpricingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = 2 + config.GlobalQueue = 2 + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, eip1559Transaction(0, 100000, keys[0], big.NewInt(1), big.NewInt(10))) + txs = append(txs, eip1559Transaction(1, 100000, keys[0], big.NewInt(2), big.NewInt(10))) + + txs = append(txs, eip1559Transaction(1, 100000, keys[1], big.NewInt(1), big.NewInt(10))) + + ltx := eip1559Transaction(0, 100000, keys[2], big.NewInt(1), big.NewInt(10)) + + // Import the batch and that both pending and queued transactions match up + pool.AddRemotes(txs) + pool.AddLocal(ltx) + + pending, queued := pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 3); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding an underpriced transaction on block limit fails + if err := pool.AddRemote(eip1559Transaction(0, 100000, keys[1], big.NewInt(1), big.NewInt(10))); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + // Ensure that adding high priced transactions drops cheap ones, but not own + if err := pool.AddRemote(eip1559Transaction(0, 100000, keys[1], big.NewInt(3), big.NewInt(10))); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, keys[1], big.NewInt(4), big.NewInt(10))); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(3, 100000, keys[1], big.NewInt(5), big.NewInt(10))); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding local transactions can push out even higher priced ones + ltx = eip1559Transaction(1, 100000, keys[2], big.NewInt(0), big.NewInt(0)) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = eip1559Transaction(0, 100000, keys[3], big.NewInt(1), big.NewInt(10)) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("local event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Test the limit on transaction size is enforced correctly. +// This test verifies every transaction having allowed size +// is added to the pool, and longer transactions are rejected. +func TestTransactionAllowedTxSize(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + defer pool.Stop() + + account := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(account, big.NewInt(1000000000)) + + // Compute maximal data size for transactions (lower bound). + // + // It is assumed the fields in the transaction (except of the data) are: + // - nonce <= 32 bytes + // - gasPrice <= 32 bytes + // - gasLimit <= 32 bytes + // - recipient == 20 bytes + // - value <= 32 bytes + // - signature == 65 bytes + // All those fields are summed up to at most 213 bytes. + baseSize := uint64(213) + dataSize := txMaxSize - baseSize + + // Try adding a transaction with maximal allowed size + tx := pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(1), key, dataSize) + if err := pool.addRemoteSync(tx); err != nil { + t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) + } + // Try adding a transaction with random allowed size + if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentMaxGas, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { + t.Fatalf("failed to add transaction of random allowed size: %v", err) + } + // Try adding a transaction of minimal not allowed size + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, txMaxSize)); err == nil { + t.Fatalf("expected rejection on slightly oversize transaction") + } + // Try adding a transaction of random not allowed size + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { + t.Fatalf("expected rejection on oversize transaction") + } + // Run some sanity checks on the pool internals + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that more expensive transactions push out cheap ones from the pool, but // without producing instability by creating gaps that start jumping transactions // back and forth between queued/pending. @@ -1447,7 +3939,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.GlobalSlots = 128 @@ -1506,13 +3998,141 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { } } +func TestTransactionPoolStableUnderpricingEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 2) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Fill up the entire queue with the same transaction price points + txs := types.Transactions{} + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, eip1559Transaction(i, 100000, keys[0], big.NewInt(1), big.NewInt(10))) + } + pool.AddRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionPoolStableUnderpricingEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 2) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Fill up the entire queue with the same transaction price points + txs := types.Transactions{} + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, eip1559Transaction(i, 100000, keys[0], big.NewInt(1), big.NewInt(10))) + } + pool.AddRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap + if err := pool.addRemoteSync(eip1559Transaction(0, 100000, keys[1], big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that the pool rejects duplicate transactions. func TestTransactionDeduplication(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1571,6 +4191,134 @@ func TestTransactionDeduplication(t *testing.T) { } } +func TestTransactionDeduplicationEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Create a batch of transactions and add a few of them + txs := make([]*types.Transaction, 16) + for i := 0; i < len(txs); i++ { + txs[i] = eip1559Transaction(uint64(i), 100000, key, big.NewInt(1), big.NewInt(10)) + } + var firsts []*types.Transaction + for i := 0; i < len(txs); i += 2 { + firsts = append(firsts, txs[i]) + } + errs := pool.AddRemotesSync(firsts) + if len(errs) != len(firsts) { + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) + } + for i, err := range errs { + if err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != len(txs)/2-1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) + } + // Try to add all of them now and ensure previous ones error out as knowns + errs = pool.AddRemotesSync(txs) + if len(errs) != len(txs) { + t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs)) + } + for i, err := range errs { + if i%2 == 0 && err == nil { + t.Errorf("add %d succeeded, should have failed as known", i) + } + if i%2 == 1 && err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued = pool.Stats() + if pending != len(txs) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionDeduplicationEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Create a batch of transactions and add a few of them + txs := make([]*types.Transaction, 16) + for i := 0; i < len(txs); i++ { + txs[i] = eip1559Transaction(uint64(i), 100000, key, big.NewInt(1), big.NewInt(10)) + } + var firsts []*types.Transaction + for i := 0; i < len(txs); i += 2 { + firsts = append(firsts, txs[i]) + } + errs := pool.AddRemotesSync(firsts) + if len(errs) != len(firsts) { + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) + } + for i, err := range errs { + if err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != len(txs)/2-1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) + } + // Try to add all of them now and ensure previous ones error out as knowns + errs = pool.AddRemotesSync(txs) + if len(errs) != len(txs) { + t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs)) + } + for i, err := range errs { + if i%2 == 0 && err == nil { + t.Errorf("add %d succeeded, should have failed as known", i) + } + if i%2 == 1 && err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued = pool.Stats() + if pending != len(txs) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that the pool rejects replacement transactions that don't meet the minimum // price bump required. func TestTransactionReplacement(t *testing.T) { @@ -1578,7 +4326,11 @@ func TestTransactionReplacement(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1651,6 +4403,162 @@ func TestTransactionReplacement(t *testing.T) { } } +func TestTransactionReplacementEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + price := int64(100) + threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100 + + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { + t.Fatalf("failed to add original cheap pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(0, 100001, key, big.NewInt(1), big.NewInt(10))); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, key, big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to replace original cheap pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("cheap replacement event firing failed: %v", err) + } + + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price+1), key)); err != nil { // price+1 to account for the baseFee of 1 + t.Fatalf("failed to add original proper pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(0, 100001, key, big.NewInt(threshold-1), big.NewInt(threshold+8))); err != ErrReplaceUnderpriced { + t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, key, big.NewInt(threshold), big.NewInt(threshold+9))); err != nil { + t.Fatalf("failed to replace original proper pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("proper replacement event firing failed: %v", err) + } + + // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add original cheap queued transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(2), key)); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(3), key)); err != nil { + t.Fatalf("failed to replace original cheap queued transaction: %v", err) + } + + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(price), big.NewInt(price+9))); err != nil { + t.Fatalf("failed to add original proper queued transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(threshold), key)); err != ErrReplaceUnderpriced { + t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(threshold+1), key)); err != nil { // threshold+1 to account for baseFee of 1 + t.Fatalf("failed to replace original proper queued transaction: %v", err) + } + + if err := validateEvents(events, 0); err != nil { + t.Fatalf("queued replacement event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +func TestTransactionReplacementEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + price := int64(100) + threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100 + + if err := pool.addRemoteSync(eip1559Transaction(0, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add original cheap pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(0, 100001, key, big.NewInt(1), big.NewInt(10))); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, key, big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to replace original cheap pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("cheap replacement event firing failed: %v", err) + } + + if err := pool.addRemoteSync(eip1559Transaction(0, 100000, key, big.NewInt(price), big.NewInt(price+9))); err != nil { + t.Fatalf("failed to add original proper pending transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(0, 100001, key, big.NewInt(threshold-1), big.NewInt(threshold+8))); err != ErrReplaceUnderpriced { + t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(0, 100000, key, big.NewInt(threshold), big.NewInt(threshold+9))); err != nil { + t.Fatalf("failed to replace original proper pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("proper replacement event firing failed: %v", err) + } + + // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add original cheap queued transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100001, key, big.NewInt(1), big.NewInt(10))); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(2), big.NewInt(10))); err != nil { + t.Fatalf("failed to replace original cheap queued transaction: %v", err) + } + + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(price), big.NewInt(price+9))); err != nil { + t.Fatalf("failed to add original proper queued transaction: %v", err) + } + if err := pool.AddRemote(eip1559Transaction(2, 100001, key, big.NewInt(threshold-1), big.NewInt(threshold+8))); err != ErrReplaceUnderpriced { + t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(eip1559Transaction(2, 100000, key, big.NewInt(threshold), big.NewInt(threshold+9))); err != nil { + t.Fatalf("failed to replace original proper queued transaction: %v", err) + } + + if err := validateEvents(events, 0); err != nil { + t.Fatalf("queued replacement event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that local transactions are journaled to disk, but remote transactions // get discarded between restarts. func TestTransactionJournaling(t *testing.T) { testTransactionJournaling(t, false) } @@ -1672,8 +4580,8 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { os.Remove(journal) // Create the original pool to inject transaction into the journal - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig config.NoLocals = nolocals @@ -1715,7 +4623,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive pool.Stop() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool = NewTxPool(config, params.TestChainConfig, blockchain) @@ -1732,6 +4640,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } } + if err := validateTxPoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1742,7 +4651,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { pool.Stop() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool = NewTxPool(config, params.TestChainConfig, blockchain) pending, queued = pool.Stats() @@ -1764,6 +4673,234 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { pool.Stop() } +func TestTransactionJournalingEIP1559(t *testing.T) { testTransactionJournalingEIP1559(t, false) } +func TestTransactionJournalingNoLocalsEIP1559(t *testing.T) { testTransactionJournalingEIP1559(t, true) } + +func testTransactionJournalingEIP1559(t *testing.T, nolocals bool) { + t.Parallel() + + // Create a temporary file for the journal + file, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("failed to create temporary journal: %v", err) + } + journal := file.Name() + defer os.Remove(journal) + + // Clean up the temporary file, we only need the path for now + file.Close() + os.Remove(journal) + + // Create the original pool to inject transaction into the journal + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.NoLocals = nolocals + config.Journal = journal + config.Rejournal = time.Second + + pool := NewTxPool(config, params.EIP1559ChainConfig, blockchain) + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add three local and a remote transactions and ensure they are queued up + if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddLocal(eip1559Transaction(1, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addRemoteSync(eip1559Transaction(0, 100000, remote, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 4 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive + pool.Stop() + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool = NewTxPool(config, params.EIP1559ChainConfig, blockchain) + + pending, queued = pool.Stats() + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if nolocals { + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + } else { + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + } + + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Bump the nonce temporarily and ensure the newly invalidated transaction is removed + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + <-pool.requestReset(nil, nil) + time.Sleep(2 * config.Rejournal) + pool.Stop() + + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + pool = NewTxPool(config, params.EIP1559ChainConfig, blockchain) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + pool.Stop() +} + +func TestTransactionJournalingEIP1559Finalized(t *testing.T) { + testTransactionJournalingEIP1559Finalized(t, false) +} +func TestTransactionJournalingNoLocalsEIP1559Finalized(t *testing.T) { + testTransactionJournalingEIP1559Finalized(t, true) +} + +func testTransactionJournalingEIP1559Finalized(t *testing.T, nolocals bool) { + t.Parallel() + + // Create a temporary file for the journal + file, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("failed to create temporary journal: %v", err) + } + journal := file.Name() + defer os.Remove(journal) + + // Clean up the temporary file, we only need the path for now + file.Close() + os.Remove(journal) + + // Create the original pool to inject transaction into the journal + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + config := testTxPoolConfig + config.NoLocals = nolocals + config.Journal = journal + config.Rejournal = time.Second + + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + pool.currentState.AddBalance(crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + pool.currentState.AddBalance(crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add three local and a remote transactions and ensure they are queued up + if err := pool.AddLocal(eip1559Transaction(0, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddLocal(eip1559Transaction(1, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.AddLocal(eip1559Transaction(2, 100000, local, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addRemoteSync(eip1559Transaction(0, 100000, remote, big.NewInt(1), big.NewInt(10))); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 4 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive + pool.Stop() + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool = NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + + pending, queued = pool.Stats() + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if nolocals { + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + } else { + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + } + + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Bump the nonce temporarily and ensure the newly invalidated transaction is removed + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + <-pool.requestReset(nil, nil) + time.Sleep(2 * config.Rejournal) + pool.Stop() + + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + pool = NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + pool.Stop() +} + // TestTransactionStatusCheck tests that the pool can correctly retrieve the // pending status of individual transactions. func TestTransactionStatusCheck(t *testing.T) { @@ -1771,7 +4908,7 @@ func TestTransactionStatusCheck(t *testing.T) { // Create the pool to test the status retrievals with statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1820,6 +4957,114 @@ func TestTransactionStatusCheck(t *testing.T) { } } +func TestTransactionStatusCheckEIP1559(t *testing.T) { + t.Parallel() + + // Create the pool to test the status retrievals with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() + + // Create the test accounts to check various transaction statuses with + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[0])) // Pending only + txs = append(txs, eip1559Transaction(0, 100000, keys[1], big.NewInt(1), big.NewInt(10))) // Pending and queued + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[1])) + txs = append(txs, eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))) // Queued only + + // Import the transaction and ensure they are correctly added + pool.AddRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Retrieve the status of each transaction and validate them + hashes := make([]common.Hash, len(txs)) + for i, tx := range txs { + hashes[i] = tx.Hash() + } + hashes = append(hashes, common.Hash{}) + + statuses := pool.Status(hashes) + expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown} + + for i := 0; i < len(statuses); i++ { + if statuses[i] != expect[i] { + t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i]) + } + } +} + +func TestTransactionStatusCheckEIP1559Finalized(t *testing.T) { + t.Parallel() + + // Create the pool to test the status retrievals with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) + defer pool.Stop() + + // Create the test accounts to check various transaction statuses with + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, eip1559Transaction(0, 100000, keys[0], big.NewInt(1), big.NewInt(10))) // Pending only + txs = append(txs, eip1559Transaction(0, 100000, keys[1], big.NewInt(1), big.NewInt(10))) // Pending and queued + txs = append(txs, eip1559Transaction(2, 100000, keys[1], big.NewInt(1), big.NewInt(10))) + txs = append(txs, eip1559Transaction(2, 100000, keys[2], big.NewInt(1), big.NewInt(10))) // Queued only + + // Import the transaction and ensure they are correctly added + pool.AddRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Retrieve the status of each transaction and validate them + hashes := make([]common.Hash, len(txs)) + for i, tx := range txs { + hashes[i] = tx.Hash() + } + hashes = append(hashes, common.Hash{}) + + statuses := pool.Status(hashes) + expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown} + + for i := 0; i < len(statuses); i++ { + if statuses[i] != expect[i] { + t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i]) + } + } +} + // Test the transaction slots consumption is computed correctly func TestTransactionSlotCount(t *testing.T) { t.Parallel() @@ -1863,6 +5108,63 @@ func benchmarkPendingDemotion(b *testing.B, size int) { } } +func BenchmarkPendingDemotionEIP1559100(b *testing.B) { benchmarkPendingDemotionEIP1559(b, 100) } +func BenchmarkPendingDemotionEIP15591000(b *testing.B) { benchmarkPendingDemotionEIP1559(b, 1000) } +func BenchmarkPendingDemotionEIP155910000(b *testing.B) { benchmarkPendingDemotionEIP1559(b, 10000) } + +func benchmarkPendingDemotionEIP1559(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupEIP1559TxPool(big.NewInt(0)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = transaction(uint64(i), 100000, key) + } else { + tx = eip1559Transaction(uint64(i), 100000, key, big.NewInt(1), big.NewInt(10)) + } + pool.promoteTx(account, tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.demoteUnexecutables() + } +} + +func BenchmarkPendingDemotionEIP1559Finalized100(b *testing.B) { + benchmarkPendingDemotionEIP1559Finalized(b, 100) +} +func BenchmarkPendingDemotionEIP1559Finalized1000(b *testing.B) { + benchmarkPendingDemotionEIP1559Finalized(b, 1000) +} +func BenchmarkPendingDemotionEIP1559Finalized10000(b *testing.B) { + benchmarkPendingDemotionEIP1559Finalized(b, 10000) +} + +func benchmarkPendingDemotionEIP1559Finalized(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(0)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := eip1559Transaction(uint64(i), 100000, key, big.NewInt(1), big.NewInt(10)) + pool.promoteTx(account, tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.demoteUnexecutables() + } +} + // Benchmarks the speed of scheduling the contents of the future queue of the // transaction pool. func BenchmarkFuturePromotion100(b *testing.B) { benchmarkFuturePromotion(b, 100) } @@ -1888,6 +5190,63 @@ func benchmarkFuturePromotion(b *testing.B, size int) { } } +func BenchmarkFuturePromotionEIP1559100(b *testing.B) { benchmarkFuturePromotionEIP1559(b, 100) } +func BenchmarkFuturePromotionEIP15591000(b *testing.B) { benchmarkFuturePromotionEIP1559(b, 1000) } +func BenchmarkFuturePromotionEIP155910000(b *testing.B) { benchmarkFuturePromotionEIP1559(b, 10000) } + +func benchmarkFuturePromotionEIP1559(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupEIP1559TxPool(big.NewInt(0)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = transaction(uint64(1+i), 100000, key) + } else { + tx = eip1559Transaction(uint64(1+i), 100000, key, big.NewInt(1), big.NewInt(10)) + } + pool.enqueueTx(tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.promoteExecutables(nil) + } +} + +func BenchmarkFuturePromotionEIP1559Finalized100(b *testing.B) { + benchmarkFuturePromotionEIP1559Finalized(b, 100) +} +func BenchmarkFuturePromotionEIP1559Finalized1000(b *testing.B) { + benchmarkFuturePromotionEIP1559Finalized(b, 1000) +} +func BenchmarkFuturePromotionEIP1559Finalized10000(b *testing.B) { + benchmarkFuturePromotionEIP1559Finalized(b, 10000) +} + +func benchmarkFuturePromotionEIP1559Finalized(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(0)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := eip1559Transaction(uint64(1+i), 100000, key, big.NewInt(1), big.NewInt(10)) + pool.enqueueTx(tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.promoteExecutables(nil) + } +} + // Benchmarks the speed of batched transaction insertion. func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100) } func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000) } @@ -1915,29 +5274,66 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) { } } -/* - We need to test: +func BenchmarkPoolBatchInsertEIP1559100(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 100) } +func BenchmarkPoolBatchInsertEIP15591000(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 1000) } +func BenchmarkPoolBatchInsertEIP155910000(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 10000) } - 1. That the tx_pool rejects EIP1559 transactions before the initialization block (validateTx) - 2. That the tx_pool accepts both types of transactions in between initialization and finalization (validateTx) - 3. That the tx_pool rejects malformed transactions that don't fit either format (validateTx) +func benchmarkPoolBatchInsertEIP1559(b *testing.B, size int) { + // Generate a batch of transactions to enqueue into the pool + pool, key := setupEIP1559TxPool(big.NewInt(0)) + defer pool.Stop() -*/ + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) -// TestEIP1559 tests the tx_pool handling of EIP1559 transactions -func TestEIP1559TxPool(t *testing.T) { - //testEIP1559TxPool(t) - //testEIP1559TxPoolAfterFinalization(t) + batches := make([]types.Transactions, b.N) + for i := 0; i < b.N; i++ { + batches[i] = make(types.Transactions, size) + for j := 0; j < size; j++ { + var tx *types.Transaction + if j%2 == 0 { + tx = transaction(uint64(size*i+j), 100000, key) + } else { + tx = eip1559Transaction(uint64(size*i+j), 100000, key, big.NewInt(1), big.NewInt(10)) + } + batches[i][j] = tx + } + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for _, batch := range batches { + pool.AddRemotes(batch) + } } -// testEIP1559TxPool tests the tx_pool handling of EIP1559 and legacy transactions during the transition phase -// in this phase both types of transactions are accepted -func testEIP1559TxPool(t *testing.T) { - panic("implement me") +func BenchmarkPoolBatchInsertEIP1559Finalized100(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 100) +} +func BenchmarkPoolBatchInsertEIP1559Finalized1000(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 1000) +} +func BenchmarkPoolBatchInsertEIP1559Finalized10000(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 10000) } -// testEIP1559TxPoolAfterFinalization tests the tx_pool handling of EIP1559 and legacy transactions after EIP1559 has been finalized -// after finalization, only EIP1559 transactions are accepted -func testEIP1559TxPoolAfterFinalization(t *testing.T) { - panic("implement me") +func benchmarkPoolBatchInsertEIP1559Finalized(b *testing.B, size int) { + // Generate a batch of transactions to enqueue into the pool + pool, key := setupEIP1559FinalizedTxPool(big.NewInt(0)) + defer pool.Stop() + + account, _ := deriveSender(eip1559Transaction(0, 0, key, big.NewInt(1), big.NewInt(10))) + pool.currentState.AddBalance(account, big.NewInt(1000000)) + + batches := make([]types.Transactions, b.N) + for i := 0; i < b.N; i++ { + batches[i] = make(types.Transactions, size) + for j := 0; j < size; j++ { + batches[i][j] = eip1559Transaction(uint64(size*i+j), 100000, key, big.NewInt(1), big.NewInt(10)) + } + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for _, batch := range batches { + pool.AddRemotes(batch) + } }