From 90e5785d9e436af4dcc6affc9ca6136565857eb6 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 14:00:20 -0600 Subject: [PATCH 01/13] IsEWASM after IsEIP1559 forks in config so we don't need EWASM engine for EIP1559 tests; gasPrice in tx needs to not be required --- core/bench_test.go | 4 ++-- core/block_validator.go | 6 +++--- core/blockchain_test.go | 16 ++++++++-------- core/types/gen_tx_json.go | 9 ++++----- core/types/transaction.go | 7 +++---- eth/downloader/testchain_test.go | 2 +- eth/fetcher/fetcher_test.go | 2 +- eth/handler_test.go | 12 ++++++------ les/handler_test.go | 2 +- light/odr_test.go | 10 +++++----- light/txpool_test.go | 2 +- miner/worker_test.go | 8 ++++---- params/config.go | 30 +++++++++++++++--------------- 13 files changed, 54 insertions(+), 56 deletions(-) diff --git a/core/bench_test.go b/core/bench_test.go index 969be3187bca..24b35bfda4e7 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -86,7 +86,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) gas, _ := IntrinsicGas(data, false, false, false) - tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data, nil, nil), types.HomesteadSigner{}, benchRootKey) + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, new(big.Int), data, nil, nil), types.HomesteadSigner{}, benchRootKey) gen.AddTx(tx) } } @@ -124,7 +124,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) { ringAddrs[to], benchRootFunds, params.TxGas, - nil, + new(big.Int), nil, nil, nil, diff --git a/core/block_validator.go b/core/block_validator.go index 4745e1ab90fd..177e3285eebb 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -148,11 +148,11 @@ func CalcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block, gas return calcGasLimitAndBaseFee(config, parent) } -// start at 50 : 50 and then shift to 0 : 100 // calcGasLimitAndBaseFee returns the EIP1559GasLimit and the BaseFee +// Start at 50 : 50 and then shift to 0 : 100 // The GasLimit for the legacy pool is (params.MaxGasEIP1559 - EIP1559GasLimit) func calcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block) (uint64, *big.Int) { - // panic if we do not have a block number set for EIP1559 activation + // Panic if we do not have a block number set for EIP1559 activation if config.EIP1559Block == nil { panic("chain config is missing EIP1559Block") } @@ -177,7 +177,7 @@ func calcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block) (ui div2 := new(big.Int).Div(div, new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator)) baseFee := new(big.Int).Add(parent.BaseFee(), div2) - // Panic is the BaseFee is not valid + // Panic if the BaseFee is not valid // A valid BASEFEE is one such that abs(BASEFEE - PARENT_BASEFEE) <= max(1, PARENT_BASEFEE // BASEFEE_MAX_CHANGE_DENOMINATOR) diff := new(big.Int).Sub(baseFee, parent.BaseFee()) if diff.Sign() < 0 { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 652889816e04..180240f01f0d 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -606,7 +606,7 @@ func TestFastVsFullChains(t *testing.T) { // If the block number is multiple of 3, send a few bonus transactions to the miner if i%3 == 2 { for j := 0; j < i%4+1; j++ { - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key) if err != nil { panic(err) } @@ -839,8 +839,8 @@ func TestChainTxReorgs(t *testing.T) { // Create two transactions shared between the chains: // - postponed: transaction included at a later block in the forked chain // - swapped: transaction included at the same block number in the forked chain - postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key1) - swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key1) + postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) // Create two transactions that will be dropped by the forked chain: // - pastDrop: transaction dropped retroactively from a past block @@ -856,13 +856,13 @@ func TestChainTxReorgs(t *testing.T) { chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) { switch i { case 0: - pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key2) + pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key2) gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork case 2: - freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key2) + freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key2) gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point gen.AddTx(swapped) // This transaction will be swapped out at the exact height @@ -881,18 +881,18 @@ func TestChainTxReorgs(t *testing.T) { chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { switch i { case 0: - pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key3) + pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key3) gen.AddTx(pastAdd) // This transaction needs to be injected during reorg case 2: gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain - freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key3) + freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key3) gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time case 3: - futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key3) + futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key3) gen.AddTx(futureAdd) // This transaction will be added after a full reorg } }) diff --git a/core/types/gen_tx_json.go b/core/types/gen_tx_json.go index 447a075d3be8..1778180e7004 100644 --- a/core/types/gen_tx_json.go +++ b/core/types/gen_tx_json.go @@ -17,7 +17,7 @@ var _ = (*txdataMarshaling)(nil) func (t txdata) MarshalJSON() ([]byte, error) { type txdata struct { AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` + Price *hexutil.Big `json:"gasPrice"` GasLimit hexutil.Uint64 `json:"gas" gencodec:"required"` Recipient *common.Address `json:"to" rlp:"nil"` Amount *hexutil.Big `json:"value" gencodec:"required"` @@ -49,7 +49,7 @@ func (t txdata) MarshalJSON() ([]byte, error) { func (t *txdata) UnmarshalJSON(input []byte) error { type txdata struct { AccountNonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` + Price *hexutil.Big `json:"gasPrice"` GasLimit *hexutil.Uint64 `json:"gas" gencodec:"required"` Recipient *common.Address `json:"to" rlp:"nil"` Amount *hexutil.Big `json:"value" gencodec:"required"` @@ -69,10 +69,9 @@ func (t *txdata) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'nonce' for txdata") } t.AccountNonce = uint64(*dec.AccountNonce) - if dec.Price == nil { - return errors.New("missing required field 'gasPrice' for txdata") + if dec.Price != nil { + t.Price = (*big.Int)(dec.Price) } - t.Price = (*big.Int)(dec.Price) if dec.GasLimit == nil { return errors.New("missing required field 'gas' for txdata") } diff --git a/core/types/transaction.go b/core/types/transaction.go index 67b9193ff4ea..1cb5a2268500 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,7 +45,7 @@ type Transaction struct { type txdata struct { AccountNonce uint64 `json:"nonce" gencodec:"required"` - Price *big.Int `json:"gasPrice" gencodec:"required"` + Price *big.Int `json:"gasPrice"` GasLimit uint64 `json:"gas" gencodec:"required"` Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation Amount *big.Int `json:"value" gencodec:"required"` @@ -95,7 +95,6 @@ func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit Payload: data, Amount: new(big.Int), GasLimit: gasLimit, - Price: new(big.Int), V: new(big.Int), R: new(big.Int), S: new(big.Int), @@ -104,7 +103,7 @@ func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit d.Amount.Set(amount) } if gasPrice != nil { - d.Price.Set(gasPrice) + d.Price = gasPrice } if gasPremium != nil { d.GasPremium = gasPremium @@ -344,7 +343,7 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) { msg := Message{ nonce: tx.data.AccountNonce, gasLimit: tx.data.GasLimit, - gasPrice: new(big.Int).Set(tx.data.Price), + gasPrice: tx.data.Price, to: tx.data.Recipient, amount: tx.data.Amount, data: tx.data.Payload, diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 4e02574f7cbb..6b56ce76f8d2 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -127,7 +127,7 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) // Include transactions to the miner to make blocks more interesting. if parent == tc.genesis && i%22 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, testKey) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, testKey) if err != nil { panic(err) } diff --git a/eth/fetcher/fetcher_test.go b/eth/fetcher/fetcher_test.go index 0f8bb695e22e..162086d9cc46 100644 --- a/eth/fetcher/fetcher_test.go +++ b/eth/fetcher/fetcher_test.go @@ -52,7 +52,7 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common // If the block number is multiple of 3, send a bonus transaction to the miner if parent == genesis && i%3 == 0 { signer := types.MakeSigner(params.TestChainConfig, block.Number()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, testKey) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, testKey) if err != nil { panic(err) } diff --git a/eth/handler_test.go b/eth/handler_test.go index 8a107c461009..d4328635362b 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -286,13 +286,13 @@ func testGetNodeData(t *testing.T, protocol int) { switch i { case 0: // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) block.AddTx(tx) case 1: // In block 2, the test bank sends some more ether to account #1. // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, acc1Key) + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, acc1Key) block.AddTx(tx1) block.AddTx(tx2) case 2: @@ -383,13 +383,13 @@ func testGetReceipt(t *testing.T, protocol int) { switch i { case 0: // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) block.AddTx(tx) case 1: // In block 2, the test bank sends some more ether to account #1. // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, acc1Key) + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, acc1Key) block.AddTx(tx1) block.AddTx(tx2) case 2: diff --git a/les/handler_test.go b/les/handler_test.go index 3f3ca17655a1..5e10cf8ee42e 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -538,7 +538,7 @@ func testTransactionStatus(t *testing.T, protocol int) { signer := types.HomesteadSigner{} // test error status by sending an underpriced transaction - tx0, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, bankKey) + tx0, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, bankKey) test(tx0, true, light.TxStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced.Error()}) tx1, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil, nil, nil), signer, bankKey) diff --git a/light/odr_test.go b/light/odr_test.go index f25ff135e7c5..ec0daa75f1a8 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -216,15 +216,15 @@ func testChainGen(i int, block *core.BlockGen) { switch i { case 0: // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) block.AddTx(tx) case 1: // In block 2, the test bank sends some more ether to account #1. // acc1Addr passes it on to account #2. // acc1Addr creates a test contract. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, testBankKey) + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, testBankKey) nonce := block.TxNonce(acc1Addr) - tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, acc1Key) + tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, acc1Key) nonce++ tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), testContractCode, nil, nil), signer, acc1Key) testContractAddr = crypto.CreateAddress(acc1Addr, nonce) @@ -236,7 +236,7 @@ func testChainGen(i int, block *core.BlockGen) { block.SetCoinbase(acc2Addr) block.SetExtra([]byte("yeehaw")) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data, nil, nil), signer, testBankKey) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, new(big.Int), data, nil, nil), signer, testBankKey) block.AddTx(tx) case 3: // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). @@ -247,7 +247,7 @@ func testChainGen(i int, block *core.BlockGen) { b3.Extra = []byte("foo") block.AddUncle(b3) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, nil, data, nil, nil), signer, testBankKey) + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, new(big.Int), data, nil, nil), signer, testBankKey) block.AddTx(tx) } } diff --git a/light/txpool_test.go b/light/txpool_test.go index 394600395649..29ffe5339a3f 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -77,7 +77,7 @@ func txPoolTestChainGen(i int, block *core.BlockGen) { func TestTxPool(t *testing.T) { for i := range testTx { - testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), types.HomesteadSigner{}, testBankKey) + testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) } var ( diff --git a/miner/worker_test.go b/miner/worker_test.go index 0a71231ad606..2c8beeaef920 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -80,9 +80,9 @@ func init() { Period: 10, Epoch: 30000, } - tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), types.HomesteadSigner{}, testBankKey) + tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) pendingTxs = append(pendingTxs, tx1) - tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), types.HomesteadSigner{}, testBankKey) + tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) newTxs = append(newTxs, tx2) rand.Seed(time.Now().UnixNano()) } @@ -170,9 +170,9 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block { func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction if creation { - tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, nil, common.FromHex(testCode), nil, nil), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, new(big.Int), common.FromHex(testCode), nil, nil), types.HomesteadSigner{}, testBankKey) } else { - tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) } return tx } diff --git a/params/config.go b/params/config.go index ec1911249a84..c5d4c70c52d2 100644 --- a/params/config.go +++ b/params/config.go @@ -66,9 +66,9 @@ var ( ConstantinopleBlock: big.NewInt(7280000), PetersburgBlock: big.NewInt(7280000), IstanbulBlock: big.NewInt(9069000), - EWASMBlock: nil, EIP1559Block: nil, EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Ethash: new(EthashConfig), } @@ -107,9 +107,9 @@ var ( ConstantinopleBlock: big.NewInt(4230000), PetersburgBlock: big.NewInt(4939394), IstanbulBlock: big.NewInt(6485846), - EWASMBlock: nil, EIP1559Block: nil, EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Ethash: new(EthashConfig), } @@ -148,9 +148,9 @@ var ( ConstantinopleBlock: big.NewInt(3660663), PetersburgBlock: big.NewInt(4321234), IstanbulBlock: big.NewInt(5435345), - EWASMBlock: nil, EIP1559Block: nil, EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -190,9 +190,9 @@ var ( ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(1561651), - EWASMBlock: nil, EIP1559Block: nil, EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -239,8 +239,8 @@ var ( TestRules = TestChainConfig.Rules(new(big.Int)) // EIP1559 test configs - EIP1559ChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, new(EthashConfig), nil} - EIP1559FinalizedChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), big.NewInt(0), new(EthashConfig), nil} + EIP1559ChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + EIP1559FinalizedChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil} EIP1559TestRules = EIP1559ChainConfig.Rules(new(big.Int)) EIP1559FinalizedTestRules = EIP1559FinalizedChainConfig.Rules(new(big.Int)) @@ -312,9 +312,9 @@ type ChainConfig struct { ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated) PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) - EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) EIP1559Block *big.Int `json:"eip1559Block,omitempty"` // EIP1559 switch block (nil = no fork, 0 = already on eip1559) EIP1559FinalizedBlock *big.Int `json:"eip1559FinalizedBlock,omitempty"` // EIP1559 finalization switch block (nil = no fork, 0 = already on eip1559 finalized) + EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` @@ -351,7 +351,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v EWASM: %v EIP1559: %v EIP1559Finalized: %v Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v EIP1559: %v EIP1559Finalized: EWASM: %v %v Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -363,9 +363,9 @@ func (c *ChainConfig) String() string { c.ConstantinopleBlock, c.PetersburgBlock, c.IstanbulBlock, - c.EWASMBlock, c.EIP1559Block, c.EIP1559FinalizedBlock, + c.EWASMBlock, engine, ) } @@ -467,9 +467,9 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {"constantinopleBlock", c.ConstantinopleBlock}, {"petersburgBlock", c.PetersburgBlock}, {"istanbulBlock", c.IstanbulBlock}, - {"ewasmBlock", c.EWASMBlock}, {"eip1559Block", c.EIP1559Block}, {"eip1559FinalizedBlock", c.EIP1559FinalizedBlock}, + {"ewasmBlock", c.EWASMBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -523,15 +523,15 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, head) { return newCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock) } - if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { - return newCompatError("EWASM fork block", c.EWASMBlock, newcfg.EWASMBlock) - } if isForkIncompatible(c.EIP1559Block, newcfg.EIP1559Block, head) { return newCompatError("EIP1559 fork block", c.EIP1559Block, newcfg.EIP1559Block) } if isForkIncompatible(c.EIP1559FinalizedBlock, newcfg.EIP1559FinalizedBlock, head) { return newCompatError("EIP1559Finalized fork block", c.EIP1559FinalizedBlock, newcfg.EIP1559FinalizedBlock) } + if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { + return newCompatError("EWASM fork block", c.EWASMBlock, newcfg.EWASMBlock) + } return nil } @@ -599,7 +599,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsEWASM, IsEIP1559, IsEIP1559Finalized bool + IsEIP1559, IsEIP1559Finalized, IsEWASM bool } // Rules ensures c's ChainID is not nil. @@ -618,8 +618,8 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsEWASM: c.IsEWASM(num), IsEIP1559: c.IsEIP1559(num), IsEIP1559Finalized: c.IsEIP1559Finalized(num), + IsEWASM: c.IsEWASM(num), } } From 15a8b7d58715ab22d3868691f5393b99e224e71f Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 14:13:35 -0600 Subject: [PATCH 02/13] evm needs to handle nil GasPrice; genesis needs to handle BaseFee --- core/evm.go | 2 +- core/genesis.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/evm.go b/core/evm.go index 662b6e960c60..e93155327e18 100644 --- a/core/evm.go +++ b/core/evm.go @@ -54,7 +54,7 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author Time: new(big.Int).SetUint64(header.Time), Difficulty: new(big.Int).Set(header.Difficulty), GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), + GasPrice: msg.GasPrice(), BaseFee: header.BaseFee, } } diff --git a/core/genesis.go b/core/genesis.go index df0c96798031..40290692f044 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -61,6 +61,7 @@ type Genesis struct { Number uint64 `json:"number"` GasUsed uint64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` + BaseFee *big.Int `json:"baseFee"` } // GenesisAlloc specifies the initial state that is part of the genesis block. @@ -277,6 +278,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { Difficulty: g.Difficulty, MixDigest: g.Mixhash, Coinbase: g.Coinbase, + BaseFee: g.BaseFee, Root: root, } if g.GasLimit == 0 { From 506cdd6740bed537cfa360cfe5b47f2cf58905c0 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 14:14:25 -0600 Subject: [PATCH 03/13] begin consensus tests --- core/block_validator_test.go | 25 +++++ core/blockchain_test.go | 27 +++++ core/chain_makers_test.go | 202 ++++++++++++++++++++++++++++++++--- core/tx_pool_test.go | 27 +++++ params/config.go | 2 +- 5 files changed, 266 insertions(+), 17 deletions(-) diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 06e2ba1a4fd9..9fed8223ed77 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -197,3 +197,28 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { t.Errorf("verification count too large: have %d, want below %d", verified, 2*threads) } } + +/* + We need to test: + + 1. That the correct, original, gasLimit is returned in the period before activation and no BaseFee is returned + 2. That both the correct EIP1559 gasLimit and BaseFee are calculated and returned in the period between activation and finalization + 3. That the entire `MaxGasEIP1559` is returned as the EIP1559 gasLimit after finalization and the correct BaseFee is calculated + +*/ + +// TestEIP1559Verification tests that verificaiton works after EIP1559 initialization and finalization +func TestEIP1559Verification(t *testing.T) { + //testEIP1559Verification(t) + //testEIP1559VerificationAfterFinalization(t) +} + +// testEIP1559Verification tests verification during the EIP1559 transition phase +func testEIP1559Verification(t *testing.T) { + panic("implement me") +} + +// testEIP1559VerificationAfterFinalization tests verification after EIP1559 finalization +func testEIP1559VerificationAfterFinalization(t *testing.T) { + panic("implement me") +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 180240f01f0d..b472162d810a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2363,3 +2363,30 @@ func TestDeleteCreateRevert(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } } + +/* + We need to test: + + 1. blockChain.insertChain works as usual before activation + 2. blockChain.insertChain works as expected during transition phase (with both types of transactions) + 3. blockChain.insertChain works as expected after finalization (with only EIP1559 transactions) + +*/ + +// TestEIP1559 tests the changes introduced by the EIP1559 forks +func TestEIP1559BlockChain(t *testing.T) { + //testEIP1559BlockChain(t) + //testEIP1559BlockChainAfterFinalization(t) +} + +// TestEIP1559Initialization tests the first EIP1559 fork which introduces a second gas pool and transaction type +// that coexists with the legacy pool and transaction type +func testEIP1559BlockChain(t *testing.T) { + panic("implement me") +} + +// TestEIP1559Finalization tests the second EIP1559 fork which finalizes the new gas pool and transaction type +// and deprecates the legacy pool and transaction type +func testEIP1559BlockChainAfterFinalization(t *testing.T) { + panic("implement me") +} diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index b482e8e57301..334cfbd65db4 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -17,8 +17,8 @@ package core import ( - "fmt" "math/big" + "testing" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -func ExampleGenerateChain() { +func TestGenerateChain(t *testing.T) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") @@ -54,13 +54,13 @@ func ExampleGenerateChain() { switch i { case 0: // In block 1, addr1 sends addr2 some ether. - tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, key1) + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) gen.AddTx(tx) case 1: // In block 2, addr1 sends some more ether to addr2. // addr2 passes it on to addr3. - tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key1) - tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, key2) + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key2) gen.AddTx(tx1) gen.AddTx(tx2) case 2: @@ -83,18 +83,188 @@ func ExampleGenerateChain() { defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { - fmt.Printf("insert error (block %d): %v\n", chain[i].NumberU64(), err) - return + t.Fatalf("insert error (block %d): %v\n", chain[i].NumberU64(), err) } state, _ := blockchain.State() - fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number()) - fmt.Println("balance of addr1:", state.GetBalance(addr1)) - fmt.Println("balance of addr2:", state.GetBalance(addr2)) - fmt.Println("balance of addr3:", state.GetBalance(addr3)) - // Output: - // last block: #5 - // balance of addr1: 989000 - // balance of addr2: 10000 - // balance of addr3: 19687500000000001000 + if blockchain.CurrentBlock().Number().Uint64() != 5 { + t.Fatalf("expected last block to equal %d got %d", 5, blockchain.CurrentBlock().Number().Uint64()) + } + if state.GetBalance(addr1).Uint64() != 989000 { + t.Fatalf("expected balance of addr1 to equal %d got %d", 989000, state.GetBalance(addr1).Uint64()) + } + if state.GetBalance(addr2).Uint64() != 10000 { + t.Fatalf("expected balance of addr2 to equal %d got %d", 10000, state.GetBalance(addr2).Uint64()) + } + bal, _ := new(big.Int).SetString("19687500000000001000", 10) + if state.GetBalance(addr3).Cmp(bal) != 0 { + t.Fatalf("expected balance of addr3 to equal %s got %d", "19687500000000001000", state.GetBalance(addr3).Uint64()) + } +} + +/* + We need to test: + + 1. GenerateChain fails with EIP1559 transactions before activation + 2. GenerateChain works with both legacy and EIP1559 transactions between activation and finalization + 3. GenerateChain fails with legacy transactions after finalization + +*/ + +// TestEIP1559 tests the changes introduced by the EIP1559 forks +func TestEIP1559GenerateChain(t *testing.T) { + generateChainBeforeActivation(t) + generateChainDuringTransition(t) + //generateChainAfterFinalization(t) +} + +// generateChainBeforeActivation +func generateChainBeforeActivation(t *testing.T) { + // We expect a panic due to an ErrTxIsEIP1559 error because of the panic at line 119 in chain_makers.go + defer func() { + if err := recover().(error); err != nil { + if err != ErrTxIsEIP1559 { + t.Fatal(err) + } + } + }() + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + db = rawdb.NewMemoryDatabase() + ) + + // Ensure that key1 has some funds in the genesis block. + gspec := &Genesis{ + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: new(big.Int), + } + genesis := gspec.MustCommit(db) + + // This call generates a chain of 5 blocks. The function runs for + // each block and adds different features to gen based on the + // block index. + signer := types.HomesteadSigner{} + chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) + defer blockchain.Stop() + + if i, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + } +} + +// generateChainDuringTransition +func generateChainDuringTransition(t *testing.T) { + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + db = rawdb.NewMemoryDatabase() + ) + + // Ensure that key1 has some funds in the genesis block. + gspec := &Genesis{ + Config: params.EIP1559ChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: new(big.Int), + } + genesis := gspec.MustCommit(db) + + // This call generates a chain of 5 blocks. The function runs for + // each block and adds different features to gen based on the + // block index. + signer := types.HomesteadSigner{} + chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) + defer blockchain.Stop() + + if i, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + } + + state, _ := blockchain.State() + if blockchain.CurrentBlock().Number().Uint64() != 5 { + t.Fatalf("expected last block to equal %d got %d", 5, blockchain.CurrentBlock().Number().Uint64()) + } + if state.GetBalance(addr1).Uint64() != 989000 { + t.Fatalf("expected balance of addr1 to equal %d got %d", 989000, state.GetBalance(addr1).Uint64()) + } + if state.GetBalance(addr2).Uint64() != 10000 { + t.Fatalf("expected balance of addr2 to equal %d got %d", 10000, state.GetBalance(addr2).Uint64()) + } + // This value is different because the test config we use has Constantinople active (uses ConstantinopleBlockReward) + bal, _ := new(big.Int).SetString("7875000000000001000", 10) + if state.GetBalance(addr3).Cmp(bal) != 0 { + t.Fatalf("expected balance of addr3 to equal %s got %d", "19687500000000001000", state.GetBalance(addr3).Uint64()) + } +} + +// generateChainAfterFinalization +func generateChainAfterFinalization(t *testing.T) { + panic("implement me") } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index a7468ddd076d..c769397626a1 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -1828,3 +1828,30 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) { pool.AddRemotes(batch) } } + +/* + We need to test: + + 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) + +*/ + +// TestEIP1559 tests the tx_pool handling of EIP1559 transactions +func TestEIP1559TxPool(t *testing.T) { + //testEIP1559TxPool(t) + //testEIP1559TxPoolAfterFinalization(t) +} + +// 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") +} + +// 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") +} diff --git a/params/config.go b/params/config.go index c5d4c70c52d2..8fbec48ee8b3 100644 --- a/params/config.go +++ b/params/config.go @@ -351,7 +351,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v EIP1559: %v EIP1559Finalized: EWASM: %v %v Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v EIP1559: %v EIP1559Finalized: %v EWASM: %v Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, From c64ae71ceaba23956a9d93c95defcc464e8ff39a Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 15:44:51 -0600 Subject: [PATCH 04/13] fix guards in simulated.callContract so that bind_tests work --- accounts/abi/bind/backends/simulated.go | 29 ++++++++++++++----------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 92e2de03c172..6215a5d1ff47 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -300,24 +300,27 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { // Ensure message is initialized properly. // EIP1559 guards + // If we have finalized EIP1559 and do not have a properly formed EIP1559 trx, sub in default values if b.config.IsEIP1559Finalized(block.Number()) && (call.GasPremium == nil || call.FeeCap == nil || call.GasPrice != nil) { - return nil, 0, false, core.ErrTxNotEIP1559 + call.GasPremium = big.NewInt(1) + call.FeeCap = big.NewInt(10) + call.GasPrice = nil } + // If we have not activated EIP1559 and do not have a properly formed legacy trx, sub in default values if !b.config.IsEIP1559(block.Number()) && (call.GasPremium != nil || call.FeeCap != nil || call.GasPrice == nil) { - return nil, 0, false, core.ErrTxIsEIP1559 - } - if call.GasPrice != nil && (call.GasPremium != nil || call.FeeCap != nil) { - return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields - } - if call.FeeCap != nil && call.GasPremium == nil { - return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set") - } - if call.GasPremium != nil && call.FeeCap == nil { - return nil, 0, false, errors.New("if GasPremium is set, FeeCap must be set") - } - if call.GasPrice == nil && call.GasPremium == nil { + call.GasPremium = nil + call.FeeCap = nil call.GasPrice = big.NewInt(1) } + // If we are in between activation and finalization + if b.config.IsEIP1559(block.Number()) && !b.config.IsEIP1559Finalized(block.Number()) { + // and we have neither a properly formed legacy or EIP1559 transaction, sub in default legacy values + if (call.GasPremium == nil || call.FeeCap == nil && call.GasPrice == nil) || (call.GasPremium != nil || call.FeeCap != nil && call.GasPrice != nil) { + call.GasPremium = nil + call.FeeCap = nil + call.GasPrice = big.NewInt(1) + } + } if call.Gas == 0 { call.Gas = 50000000 } From f57a93d1e667ac8fb7320573824b8d5287719ff8 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 15:46:34 -0600 Subject: [PATCH 05/13] mobile feeCap and gasPremium => *BigInt instead of *big.Int; add getter and setter methods --- les/test_helper.go | 12 ++++++------ mobile/ethereum.go | 6 +++++- mobile/types.go | 11 +++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/les/test_helper.go b/les/test_helper.go index e3ba0207969a..665380a3d931 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -111,18 +111,18 @@ func prepare(n int, backend *backends.SimulatedBackend) { registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) - tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil, nil, nil), signer, bankKey) + tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx) case 1: bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) // bankUser transfers more ether to user1 - tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, bankKey) + tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx1) // user1 relays ether to user2 - tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, nil, nil, nil, nil), signer, userKey1) + tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, userKey1) backend.SendTransaction(ctx, tx2) // user1 deploys a test contract @@ -136,18 +136,18 @@ func prepare(n int, backend *backends.SimulatedBackend) { case 2: // bankUser transfer some ether to signer bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) - tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil, nil, nil), signer, bankKey) + tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, new(big.Int), nil, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx1) // invoke test contract data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data, nil, nil), signer, bankKey) + tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, new(big.Int), data, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx2) case 3: // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, nil, data, nil, nil), signer, bankKey) + tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, new(big.Int), data, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx) } backend.Commit() diff --git a/mobile/ethereum.go b/mobile/ethereum.go index 59da85239744..258ecfa8b637 100644 --- a/mobile/ethereum.go +++ b/mobile/ethereum.go @@ -21,7 +21,7 @@ package geth import ( "errors" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" ) @@ -58,6 +58,8 @@ func (msg *CallMsg) GetTo() *Address { } return nil } +func (msg *CallMsg) GetGasPremium() *BigInt { return &BigInt{msg.msg.GasPremium} } +func (msg *CallMsg) GetFeeCap() *BigInt { return &BigInt{msg.msg.FeeCap} } func (msg *CallMsg) SetFrom(address *Address) { msg.msg.From = address.address } func (msg *CallMsg) SetGas(gas int64) { msg.msg.Gas = uint64(gas) } @@ -71,6 +73,8 @@ func (msg *CallMsg) SetTo(address *Address) { } msg.msg.To = &address.address } +func (msg *CallMsg) SetGasPremium(gasPremium *BigInt) { msg.msg.GasPremium = gasPremium.bigint } +func (msg *CallMsg) SetFeeCap(feeCap *BigInt) { msg.msg.FeeCap = feeCap.bigint } // SyncProgress gives progress indications when the node is synchronising with // the Ethereum network. diff --git a/mobile/types.go b/mobile/types.go index 16ee48146d63..d134ea7a60d4 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -22,7 +22,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -200,17 +199,17 @@ type Transaction struct { // NewContractCreation creates a new transaction for deploying a new contract with // the given properties. -func NewContractCreation(nonce int64, amount *BigInt, gasLimit int64, gasPrice *BigInt, data []byte, gasPremium, feeCap *big.Int) *Transaction { - return &Transaction{types.NewContractCreation(uint64(nonce), amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium, feeCap)} +func NewContractCreation(nonce int64, amount *BigInt, gasLimit int64, gasPrice *BigInt, data []byte, gasPremium, feeCap *BigInt) *Transaction { + return &Transaction{types.NewContractCreation(uint64(nonce), amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium.bigint, feeCap.bigint)} } // NewTransaction creates a new transaction with the given properties. Contracts // can be created by transacting with a nil recipient. -func NewTransaction(nonce int64, to *Address, amount *BigInt, gasLimit int64, gasPrice *BigInt, data []byte, gasPremium, feeCap *big.Int) *Transaction { +func NewTransaction(nonce int64, to *Address, amount *BigInt, gasLimit int64, gasPrice *BigInt, data []byte, gasPremium, feeCap *BigInt) *Transaction { if to == nil { - return &Transaction{types.NewContractCreation(uint64(nonce), amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium, feeCap)} + return &Transaction{types.NewContractCreation(uint64(nonce), amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium.bigint, feeCap.bigint)} } - return &Transaction{types.NewTransaction(uint64(nonce), to.address, amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium, feeCap)} + return &Transaction{types.NewTransaction(uint64(nonce), to.address, amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data), gasPremium.bigint, feeCap.bigint)} } // NewTransactionFromRLP parses a transaction from an RLP data dump. From 3933ee7f225420fad8b1fef7ce815708b247bcc5 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 6 Dec 2019 16:10:00 -0600 Subject: [PATCH 06/13] finish chain_maker_tests for EIP1559 --- core/chain_makers_test.go | 160 ++++++++++++++++++++++++++++++++++---- 1 file changed, 146 insertions(+), 14 deletions(-) diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 334cfbd65db4..524db2eb5ab6 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -102,23 +102,15 @@ func TestGenerateChain(t *testing.T) { } } -/* - We need to test: - - 1. GenerateChain fails with EIP1559 transactions before activation - 2. GenerateChain works with both legacy and EIP1559 transactions between activation and finalization - 3. GenerateChain fails with legacy transactions after finalization - -*/ - // TestEIP1559 tests the changes introduced by the EIP1559 forks func TestEIP1559GenerateChain(t *testing.T) { generateChainBeforeActivation(t) generateChainDuringTransition(t) - //generateChainAfterFinalization(t) + generateChainAfterFinalization(t) + generateChainAfterFinalization2(t) } -// generateChainBeforeActivation +// generateChainBeforeActivation demonstrates that we panic if we try to make a chain with EIP1559 transactions before EIP1559 activation func generateChainBeforeActivation(t *testing.T) { // We expect a panic due to an ErrTxIsEIP1559 error because of the panic at line 119 in chain_makers.go defer func() { @@ -187,7 +179,7 @@ func generateChainBeforeActivation(t *testing.T) { } } -// generateChainDuringTransition +// generateChainDuringTransition demonstrates that we can make a chain with both legacy and EIP1559 transactions during the transition phase func generateChainDuringTransition(t *testing.T) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -264,7 +256,147 @@ func generateChainDuringTransition(t *testing.T) { } } -// generateChainAfterFinalization +// generateChainAfterFinalization demonstrates that we panic if we try to make a chain with legacy transactions after EIP1559 finalization func generateChainAfterFinalization(t *testing.T) { - panic("implement me") + defer func() { + if err := recover().(error); err != nil { + if err != ErrTxNotEIP1559 { + t.Fatal(err) + } + } + }() + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + db = rawdb.NewMemoryDatabase() + ) + + // Ensure that key1 has some funds in the genesis block. + gspec := &Genesis{ + Config: params.EIP1559FinalizedChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: new(big.Int), + } + genesis := gspec.MustCommit(db) + + // This call generates a chain of 5 blocks. The function runs for + // each block and adds different features to gen based on the + // block index. + signer := types.HomesteadSigner{} + chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) + defer blockchain.Stop() + + if i, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + } +} + +// generateChainAfterFinalization2 demonstrates that we can build a chain post EIP1559 finalization with EIP1559 transactions +func generateChainAfterFinalization2(t *testing.T) { + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + db = rawdb.NewMemoryDatabase() + ) + + // Ensure that key1 has some funds in the genesis block. + gspec := &Genesis{ + Config: params.EIP1559FinalizedChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: new(big.Int), + } + genesis := gspec.MustCommit(db) + + // This call generates a chain of 5 blocks. The function runs for + // each block and adds different features to gen based on the + // block index. + signer := types.HomesteadSigner{} + chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) + defer blockchain.Stop() + + if i, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + } + + state, _ := blockchain.State() + if blockchain.CurrentBlock().Number().Uint64() != 5 { + t.Fatalf("expected last block to equal %d got %d", 5, blockchain.CurrentBlock().Number().Uint64()) + } + if state.GetBalance(addr1).Uint64() != 989000 { + t.Fatalf("expected balance of addr1 to equal %d got %d", 989000, state.GetBalance(addr1).Uint64()) + } + if state.GetBalance(addr2).Uint64() != 10000 { + t.Fatalf("expected balance of addr2 to equal %d got %d", 10000, state.GetBalance(addr2).Uint64()) + } + // This value is different than in TestGenerateChain because the test config we use has Constantinople active (uses ConstantinopleBlockReward) + bal, _ := new(big.Int).SetString("7875000000000001000", 10) + if state.GetBalance(addr3).Cmp(bal) != 0 { + t.Fatalf("expected balance of addr3 to equal %s got %d", "19687500000000001000", state.GetBalance(addr3).Uint64()) + } } From 93871eda2bc14313f8390a8044c2496930aec127 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Mon, 9 Dec 2019 14:52:02 -0600 Subject: [PATCH 07/13] transaction.Cost() needs to be aware of BaseFee to calculate cost for EIP1559 trxs; tx_list.go needs to use derived GasPrice for EIP1559 transactions and priceHeap needs to be aware of BaseFee to sort by both legacy and EIP1559 prices --- core/state_transition.go | 2 +- core/tx_list.go | 130 ++++++++++++++++++++++++++++++-------- core/tx_list_test.go | 2 +- core/tx_pool.go | 19 +++--- core/types/transaction.go | 22 +++++-- light/txpool.go | 19 +++++- mobile/types.go | 5 +- 7 files changed, 153 insertions(+), 46 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 9139bf46d6a5..9f1e021ea842 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -121,7 +121,7 @@ func IntrinsicGas(data []byte, contractCreation, isEIP155 bool, isEIP2028 bool) // NewStateTransition initialises and returns a new state transition object. func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTransition { - isEIP1559 := evm.ChainConfig().IsEIP1559(evm.BlockNumber) && msg.GasPremium() != nil && msg.FeeCap() != nil && evm.BaseFee != nil && gp1559 != nil + isEIP1559 := evm.ChainConfig().IsEIP1559(evm.BlockNumber) && msg.GasPrice() == nil && msg.GasPremium() != nil && msg.FeeCap() != nil && evm.BaseFee != nil && gp1559 != nil st := &StateTransition{ gp: gp, gp1559: gp1559, diff --git a/core/tx_list.go b/core/tx_list.go index 75bfdaedac1a..6b87b2d667d5 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -248,21 +248,52 @@ func (l *txList) Overlaps(tx *types.Transaction) bool { // // If the new transaction is accepted into the list, the lists' cost and gas // thresholds are also potentially updated. -func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { +func (l *txList) Add(tx *types.Transaction, priceBump uint64, baseFee *big.Int) (bool, *types.Transaction) { // If there's an older better transaction, abort old := l.txs.Get(tx.Nonce()) if old != nil { - threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100)) // Have to ensure that the new gas price is higher than the old gas // price as well as checking the percentage threshold to ensure that // this is accurate for low (Wei-level) gas price replacements - if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 { - return false, nil + if old.GasPrice() != nil { + threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100)) + if tx.GasPrice() != nil { + if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 { + return false, nil + } + } else { + newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium()) + if newGasPrice.Cmp(tx.FeeCap()) > 0 { + newGasPrice.Set(tx.FeeCap()) + } + if old.GasPrice().Cmp(newGasPrice) >= 0 || threshold.Cmp(newGasPrice) > 0 { + return false, nil + } + } + } else { + oldGasPrice := new(big.Int).Add(baseFee, old.GasPremium()) + if oldGasPrice.Cmp(old.FeeCap()) > 0 { + oldGasPrice.Set(old.FeeCap()) + } + threshold := new(big.Int).Div(new(big.Int).Mul(oldGasPrice, big.NewInt(100+int64(priceBump))), big.NewInt(100)) + if tx.GasPrice() != nil { + if oldGasPrice.Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 { + return false, nil + } + } else { + newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium()) + if newGasPrice.Cmp(tx.FeeCap()) > 0 { + newGasPrice.Set(tx.FeeCap()) + } + if oldGasPrice.Cmp(newGasPrice) >= 0 || threshold.Cmp(newGasPrice) > 0 { + return false, nil + } + } } } // Otherwise overwrite the old transaction with the current one l.txs.Put(tx) - if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { + if cost := tx.Cost(baseFee); l.costcap.Cmp(cost) < 0 { l.costcap = cost } if gas := tx.Gas(); l.gascap < gas { @@ -287,7 +318,7 @@ func (l *txList) Forward(threshold uint64) types.Transactions { // a point in calculating all the costs or if the balance covers all. If the threshold // is lower than the costgas cap, the caps will be reset to a new high after removing // the newly invalidated transactions. -func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) { +func (l *txList) Filter(costLimit *big.Int, gasLimit uint64, baseFee *big.Int) (types.Transactions, types.Transactions) { // If all transactions are below the threshold, short circuit if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit { return nil, nil @@ -296,7 +327,7 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions l.gascap = gasLimit // Filter out all the transactions above the account's funds - removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit }) + removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost(baseFee).Cmp(costLimit) > 0 || tx.Gas() > gasLimit }) // If the list was strict, filter anything above the lowest nonce var invalids types.Transactions @@ -365,32 +396,49 @@ func (l *txList) Flatten() types.Transactions { // priceHeap is a heap.Interface implementation over transactions for retrieving // price-sorted transactions to discard when the pool fills up. -type priceHeap []*types.Transaction +type priceHeap struct { + txs []*types.Transaction + baseFee *big.Int +} -func (h priceHeap) Len() int { return len(h) } -func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h priceHeap) Len() int { return len(h.txs) } +func (h priceHeap) Swap(i, j int) { h.txs[i], h.txs[j] = h.txs[j], h.txs[i] } func (h priceHeap) Less(i, j int) bool { // Sort primarily by price, returning the cheaper one - switch h[i].GasPrice().Cmp(h[j].GasPrice()) { + iPrice := h.txs[i].GasPrice() + jPrice := h.txs[j].GasPrice() + if iPrice == nil { + iPrice = new(big.Int).Add(h.baseFee, h.txs[i].GasPremium()) + if iPrice.Cmp(h.txs[i].FeeCap()) > 0 { + iPrice.Set(h.txs[i].FeeCap()) + } + } + if jPrice == nil { + jPrice = new(big.Int).Add(h.baseFee, h.txs[j].GasPremium()) + if jPrice.Cmp(h.txs[j].FeeCap()) > 0 { + jPrice.Set(h.txs[j].FeeCap()) + } + } + switch iPrice.Cmp(jPrice) { case -1: return true case 1: return false } // If the prices match, stabilize via nonces (high nonce is worse) - return h[i].Nonce() > h[j].Nonce() + return h.txs[i].Nonce() > h.txs[j].Nonce() } func (h *priceHeap) Push(x interface{}) { - *h = append(*h, x.(*types.Transaction)) + h.txs = append(h.txs, x.(*types.Transaction)) } func (h *priceHeap) Pop() interface{} { - old := *h + old := h.txs n := len(old) x := old[n-1] - *h = old[0 : n-1] + h.txs = old[0 : n-1] return x } @@ -403,10 +451,13 @@ type txPricedList struct { } // newTxPricedList creates a new price-sorted transaction heap. -func newTxPricedList(all *txLookup) *txPricedList { +func newTxPricedList(all *txLookup, baseFee *big.Int) *txPricedList { + pHeap := new(priceHeap) + pHeap.txs = make([]*types.Transaction, 0) + pHeap.baseFee = baseFee return &txPricedList{ all: all, - items: new(priceHeap), + items: pHeap, } } @@ -421,15 +472,17 @@ func (l *txPricedList) Put(tx *types.Transaction) { func (l *txPricedList) Removed(count int) { // Bump the stale counter, but exit if still too low (< 25%) l.stales += count - if l.stales <= len(*l.items)/4 { + if l.stales <= len(l.items.txs)/4 { return } // Seems we've reached a critical number of stale transactions, reheap - reheap := make(priceHeap, 0, l.all.Count()) + reheap := priceHeap{} + reheap.txs = make([]*types.Transaction, 0, l.all.Count()) + reheap.baseFee = l.items.baseFee l.stales, l.items = 0, &reheap l.all.Range(func(hash common.Hash, tx *types.Transaction) bool { - *l.items = append(*l.items, tx) + l.items.txs = append(l.items.txs, tx) return true }) heap.Init(l.items) @@ -441,7 +494,7 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep - for len(*l.items) > 0 { + for len(l.items.txs) > 0 { // Discard stale transactions if found during cleanup tx := heap.Pop(l.items).(*types.Transaction) if l.all.Get(tx.Hash()) == nil { @@ -449,7 +502,14 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact continue } // Stop the discards if we've reached the threshold - if tx.GasPrice().Cmp(threshold) >= 0 { + gasPrice := tx.GasPrice() + if gasPrice == nil { + gasPrice = new(big.Int).Add(l.items.baseFee, tx.GasPremium()) + if gasPrice.Cmp(tx.FeeCap()) > 0 { + gasPrice.Set(tx.FeeCap()) + } + } + if gasPrice.Cmp(threshold) >= 0 { save = append(save, tx) break } @@ -474,8 +534,8 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo return false } // Discard stale price points if found at the heap start - for len(*l.items) > 0 { - head := []*types.Transaction(*l.items)[0] + for len(l.items.txs) > 0 { + head := []*types.Transaction(l.items.txs)[0] if l.all.Get(head.Hash()) == nil { l.stales-- heap.Pop(l.items) @@ -484,12 +544,26 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo break } // Check if the transaction is underpriced or not - if len(*l.items) == 0 { + if len(l.items.txs) == 0 { log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors return false } - cheapest := []*types.Transaction(*l.items)[0] - return cheapest.GasPrice().Cmp(tx.GasPrice()) >= 0 + cheapest := []*types.Transaction(l.items.txs)[0] + cheapestPrice := cheapest.GasPrice() + if cheapestPrice == nil { + cheapestPrice = new(big.Int).Add(l.items.baseFee, cheapest.GasPremium()) + if cheapestPrice.Cmp(cheapest.FeeCap()) > 0 { + cheapestPrice.Set(cheapest.FeeCap()) + } + } + txPrice := tx.GasPrice() + if txPrice == nil { + txPrice = new(big.Int).Add(l.items.baseFee, tx.GasPremium()) + if txPrice.Cmp(tx.FeeCap()) > 0 { + txPrice.Set(tx.FeeCap()) + } + } + return cheapestPrice.Cmp(txPrice) >= 0 } // Discard finds a number of most underpriced transactions, removes them from the @@ -498,7 +572,7 @@ func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions drop := make(types.Transactions, 0, count) // Remote underpriced transactions to drop save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep - for len(*l.items) > 0 && count > 0 { + for len(l.items.txs) > 0 && count > 0 { // Discard stale transactions if found during cleanup tx := heap.Pop(l.items).(*types.Transaction) if l.all.Get(tx.Hash()) == nil { diff --git a/core/tx_list_test.go b/core/tx_list_test.go index d579f501afa8..0248b2b8bfbc 100644 --- a/core/tx_list_test.go +++ b/core/tx_list_test.go @@ -37,7 +37,7 @@ func TestStrictTxListAdd(t *testing.T) { // Insert the transactions in a random order list := newTxList(true) for _, v := range rand.Perm(len(txs)) { - list.Add(txs[v], DefaultTxPoolConfig.PriceBump) + list.Add(txs[v], DefaultTxPoolConfig.PriceBump, nil) } // Verify internal state if len(list.txs.items) != len(txs) { diff --git a/core/tx_pool.go b/core/tx_pool.go index 8aa3eac62635..f151d282fd9b 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -295,7 +295,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block log.Info("Setting new local account", "address", addr) pool.locals.add(addr) } - pool.priced = newTxPricedList(pool.all) + pool.priced = newTxPricedList(pool.all, chain.CurrentBlock().BaseFee()) pool.reset(nil, chain.CurrentBlock().Header()) // Start the reorg loop early so it can handle requests generated during journal loading. @@ -370,6 +370,7 @@ func (pool *TxPool) loop() { // Handle inactive account transaction eviction case <-evict.C: pool.mu.Lock() + pool.priced.items.baseFee = pool.chain.CurrentBlock().BaseFee() for addr := range pool.queue { // Skip local transactions from the eviction mechanism if pool.locals.contains(addr) { @@ -431,7 +432,7 @@ func (pool *TxPool) GasPrice() *big.Int { func (pool *TxPool) SetGasPrice(price *big.Int) { pool.mu.Lock() defer pool.mu.Unlock() - + pool.priced.items.baseFee = pool.chain.CurrentBlock().BaseFee() pool.gasPrice = price for _, tx := range pool.priced.Cap(price, pool.locals) { pool.removeTx(tx.Hash(), false) @@ -585,7 +586,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { } // Transactor should have enough funds to cover the costs // cost == V + GP * GL - if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 { + if pool.currentState.GetBalance(from).Cmp(tx.Cost(pool.chain.CurrentBlock().BaseFee())) < 0 { return ErrInsufficientFunds } // Ensure the transaction has more gas than the basic tx fee. @@ -607,6 +608,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { // whitelisted, preventing any associated transaction from being dropped out of the pool // due to pricing constraints. func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { + pool.priced.items.baseFee = pool.chain.CurrentBlock().BaseFee() // If the transaction is already known, discard it hash := tx.Hash() if pool.all.Get(hash) != nil { @@ -640,7 +642,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e from, _ := types.Sender(pool.signer, tx) // already validated if list := pool.pending[from]; list != nil && list.Overlaps(tx) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.config.PriceBump, pool.chain.CurrentBlock().BaseFee()) if !inserted { pendingDiscardMeter.Mark(1) return false, ErrReplaceUnderpriced @@ -688,7 +690,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er if pool.queue[from] == nil { pool.queue[from] = newTxList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, pool.chain.CurrentBlock().BaseFee()) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -733,7 +735,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T } list := pool.pending[addr] - inserted, old := list.Add(tx, pool.config.PriceBump) + inserted, old := list.Add(tx, pool.config.PriceBump, pool.chain.CurrentBlock().BaseFee()) if !inserted { // An older transaction was better, discard this pool.all.Remove(hash) @@ -1057,6 +1059,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt promoteAddrs = dirtyAccounts.flatten() } pool.mu.Lock() + pool.priced.items.baseFee = pool.chain.CurrentBlock().BaseFee() if reset != nil { // Reset from the old head to the new, rescheduling any reorged transactions pool.reset(reset.oldHead, reset.newHead) @@ -1219,7 +1222,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans log.Trace("Removed old queued transaction", "hash", hash) } // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas, pool.chain.CurrentBlock().BaseFee()) for _, tx := range drops { hash := tx.Hash() pool.all.Remove(hash) @@ -1411,7 +1414,7 @@ func (pool *TxPool) demoteUnexecutables() { log.Trace("Removed old pending transaction", "hash", hash) } // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas, pool.chain.CurrentBlock().BaseFee()) for _, tx := range drops { hash := tx.Hash() log.Trace("Removed unpayable pending transaction", "hash", hash) diff --git a/core/types/transaction.go b/core/types/transaction.go index 1cb5a2268500..23a833ab6a35 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -294,7 +294,7 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } +func (tx *Transaction) GasPrice() *big.Int { return tx.data.Price } func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } func (tx *Transaction) CheckNonce() bool { return true } @@ -370,10 +370,22 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e } // Cost returns amount + gasprice * gaslimit. -func (tx *Transaction) Cost() *big.Int { - total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit)) - total.Add(total, tx.data.Amount) - return total +func (tx *Transaction) Cost(baseFee *big.Int) *big.Int { + if tx.data.Price != nil { + total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit)) + total.Add(total, tx.data.Amount) + return total + } + if baseFee != nil && tx.data.GasPremium != nil && tx.data.FeeCap != nil { + eip1559GasPrice := new(big.Int).Add(baseFee, tx.data.GasPremium) + if eip1559GasPrice.Cmp(tx.data.FeeCap) > 0 { + eip1559GasPrice.Set(tx.data.FeeCap) + } + total := new(big.Int).Mul(eip1559GasPrice, new(big.Int).SetUint64(tx.data.GasLimit)) + total.Add(total, tx.data.Amount) + return total + } + return nil } // RawSignatureValues returns the V, R, S signature values of the transaction. diff --git a/light/txpool.go b/light/txpool.go index 11a0e76ae01e..c57a54ddbddb 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -343,6 +343,23 @@ func (pool *TxPool) Stats() (pending int) { // validateTx checks whether a transaction is valid according to the consensus rules. func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error { + // EIP1559 guards + if pool.config.IsEIP1559(pool.chain.CurrentHeader().Number) && pool.chain.CurrentHeader().BaseFee == nil { + return core.ErrNoBaseFee + } + if pool.config.IsEIP1559Finalized(pool.chain.CurrentHeader().Number) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return core.ErrTxNotEIP1559 + } + if !pool.config.IsEIP1559(pool.chain.CurrentHeader().Number) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return core.ErrTxIsEIP1559 + } + if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { + return core.ErrTxSetsLegacyAndEIP1559Fields + } + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return core.ErrMissingGasFields + } + // Validate sender var ( from common.Address @@ -376,7 +393,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error // Transactor should have enough funds to cover the costs // cost == V + GP * GL - if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { + if b := currentState.GetBalance(from); b.Cmp(tx.Cost(pool.chain.CurrentHeader().BaseFee)) < 0 { return core.ErrInsufficientFunds } diff --git a/mobile/types.go b/mobile/types.go index d134ea7a60d4..328f5cc94c41 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -251,8 +252,8 @@ func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} func (tx *Transaction) GetValue() *BigInt { return &BigInt{tx.tx.Value()} } func (tx *Transaction) GetNonce() int64 { return int64(tx.tx.Nonce()) } -func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} } -func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} } +func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} } +func (tx *Transaction) GetCost(baseFee *big.Int) *BigInt { return &BigInt{tx.tx.Cost(baseFee)} } // Deprecated: GetSigHash cannot know which signer to use. func (tx *Transaction) GetSigHash() *Hash { return &Hash{types.HomesteadSigner{}.Hash(tx.tx)} } From d94599d91a5dd27cea31d3e0e9475ff1bfd51722 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Mon, 9 Dec 2019 14:52:34 -0600 Subject: [PATCH 08/13] tx_pool_tests and benchmarks for EIP1559 --- core/tx_pool_test.go | 4804 +++++++++++++++++++++++++++++++++++------- 1 file changed, 4099 insertions(+), 705 deletions(-) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index c769397626a1..a78b979121e2 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) } @@ -77,9 +79,19 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec 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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} key, _ := crypto.GenerateKey() pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -87,6 +99,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() @@ -187,7 +219,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) @@ -221,6 +253,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() @@ -256,6 +417,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) { @@ -315,37 +605,237 @@ func TestTransactionQueue2(t *testing.T) { } } -func TestTransactionNegativeValue(t *testing.T) { +func TestTransactionQueueEIP1559(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupEIP1559TxPool(big.NewInt(5)) defer pool.Stop() - tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil, nil, nil), types.HomesteadSigner{}, key) + tx := transaction(0, 100, key) from, _ := deriveSender(tx) - pool.currentState.AddBalance(from, big.NewInt(1)) - if err := pool.AddRemote(tx); err != ErrNegativeValue { - t.Error("expected", ErrNegativeValue, "got", err) + 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 TestTransactionChainFork(t *testing.T) { +func TestTransactionQueueEIP1559Finalized(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + 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)) + 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.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} - <-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)) } - resetState() - tx := transaction(0, 100000, key) + 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() + + pool, key := setupTxPool() + defer pool.Stop() + + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil, nil, nil), 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 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() + + pool, key := setupTxPool() + 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), nil} + <-pool.requestReset(nil, nil) + } + resetState() + + tx := transaction(0, 100000, key) + 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 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) } @@ -369,7 +859,7 @@ func TestTransactionDoubleNonce(t *testing.T) { 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() @@ -409,66 +899,318 @@ func TestTransactionDoubleNonce(t *testing.T) { } } -func TestTransactionMissingNonce(t *testing.T) { +func TestTransactionDoubleNonceEIP1559(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + 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) + 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) } - if len(pool.pending) != 0 { - t.Error("expected 0 pending transactions, got", len(pool.pending)) + 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 pool.queue[addr].Len() != 1 { - t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + 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 TestTransactionNonceRecovery(t *testing.T) { +func TestTransactionDoubleNonceEIP1559Finalized(t *testing.T) { t.Parallel() - const n = 10 - pool, key := setupTxPool() + 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) + resetState := func() { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb.AddBalance(addr, big.NewInt(100000000000000)) - 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) + pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(5)} + <-pool.requestReset(nil, nil) } -} - -// Tests that if an account runs out of funds, any pending and queued transactions -// are dropped. -func TestTransactionDropping(t *testing.T) { - t.Parallel() - - // Create a test account and fund it - pool, key := setupTxPool() - defer pool.Stop() + resetState() - account, _ := deriveSender(transaction(0, 0, key)) - 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 some pending and some queued transactions + // 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() + + pool, key := setupTxPool() + 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()) + } +} + +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() + + const n = 10 + pool, key := setupTxPool() + 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 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) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + 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) tx1 = transaction(1, 200, key) @@ -512,19 +1254,19 @@ 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) @@ -550,62 +1292,246 @@ func TestTransactionDropping(t *testing.T) { } } -// Tests that if a transaction is dropped from the current pending pool (e.g. out -// of fund), all consecutive (still valid, but not executable) transactions are -// postponed back into the future queue to prevent broadcasting them. -func TestTransactionPostponing(t *testing.T) { +func TestTransactionDroppingEIP1559(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)} - - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + // Create a test account and fund it + pool, key := setupEIP1559TxPool(big.NewInt(1)) 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) + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1000)) - pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100)) - } - // Add a batch consecutive pending transactions for validation - txs := []*types.Transaction{} - for i, key := range keys { + // 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) - for j := 0; j < 100; j++ { - var tx *types.Transaction - if (i+j)%2 == 0 { - tx = transaction(uint64(j), 25000, key) - } else { - tx = transaction(uint64(j), 50000, 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 pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - if len(pool.queue) != 0 { - t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) } - if pool.all.Count() != len(txs) { - t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) } <-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 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) + } + // 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) + } +} + +// Tests that if a transaction is dropped from the current pending pool (e.g. out +// of fund), all consecutive (still valid, but not executable) transactions are +// postponed back into the future queue to prevent broadcasting them. +func TestTransactionPostponing(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), nil} + + pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, 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(50100)) + } + // 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 = transaction(uint64(j), 25000, key) + } else { + tx = transaction(uint64(j), 50000, 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) } @@ -664,435 +1590,2360 @@ func TestTransactionPostponing(t *testing.T) { } } -// 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. -func TestTransactionGapFilling(t *testing.T) { +func TestTransactionPostponingEIP1559(t *testing.T) { t.Parallel() - // Create a test account and fund it - pool, key := setupTxPool() + // 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() - account, _ := deriveSender(transaction(0, 0, key)) - pool.currentState.AddBalance(account, big.NewInt(1000000)) + // Create two test accounts to produce different gap profiles with + keys := make([]*ecdsa.PrivateKey, 2) + accs := make([]common.Address, len(keys)) - // 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() + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) - // Create a pending and a queued transaction with a nonce-gap in between - pool.AddRemotesSync([]*types.Transaction{ - transaction(0, 100000, key), - transaction(2, 100000, key), - }) - pending, queued := pool.Stats() - if pending != 1 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(100100)) } - if queued != 1 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + // 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) + } } - if err := validateEvents(events, 1); err != nil { - t.Fatalf("original event firing failed: %v", err) + for i, err := range pool.AddRemotesSync(txs) { + if err != nil { + t.Fatalf("tx %d: failed to add transactions: %v", i, err) + } } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", 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)) } - // 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) + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } - pending, queued = pool.Stats() - if pending != 3 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) } - if queued != 0 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + <-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 err := validateEvents(events, 2); err != nil { - t.Fatalf("gap-filling event firing failed: %v", err) + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) + 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 count belonging to a single account goes above -// some threshold, the higher transactions are dropped to prevent DOS attacks. -func TestTransactionQueueAccountLimiting(t *testing.T) { +func TestTransactionPostponingEIP1559Finalized(t *testing.T) { t.Parallel() - // Create a test account and fund it - pool, key := setupTxPool() + // 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() - account, _ := deriveSender(transaction(0, 0, key)) - pool.currentState.AddBalance(account, big.NewInt(1000000)) + // Create two test accounts to produce different gap profiles with + keys := make([]*ecdsa.PrivateKey, 2) + accs := make([]common.Address, len(keys)) - // 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(transaction(i, 100000, key)); err != nil { - t.Fatalf("tx %d: failed to add transaction: %v", i, err) + 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) } - if len(pool.pending) != 0 { - t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) + } + for i, err := range pool.AddRemotesSync(txs) { + if err != nil { + t.Fatalf("tx %d: failed to add transactions: %v", i, err) } - 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) + } + // 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 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 _, 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) } } } - if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { - t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) + // 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. +func TestTransactionGapFilling(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + 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), + transaction(2, 100000, key), + }) + 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) } } -// Tests that if the transaction count belonging to multiple accounts go above -// some threshold, the higher transactions are dropped to prevent DOS attacks. -// -// This logic should not hold for local transactions, unless the local tracking -// mechanism is disabled. -func TestTransactionQueueGlobalLimiting(t *testing.T) { - testTransactionQueueGlobalLimiting(t, false) -} -func TestTransactionQueueGlobalLimitingNoLocals(t *testing.T) { - testTransactionQueueGlobalLimiting(t, true) +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) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + 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(transaction(i, 100000, key)); 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 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. +// +// This logic should not hold for local transactions, unless the local tracking +// mechanism is disabled. +func TestTransactionQueueGlobalLimiting(t *testing.T) { + testTransactionQueueGlobalLimiting(t, false) +} +func TestTransactionQueueGlobalLimitingNoLocals(t *testing.T) { + testTransactionQueueGlobalLimiting(t, true) +} + +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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + 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.TestChainConfig, 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, transaction(nonces[addr]+1, 100000, key)) + 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 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, 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), nil} + + config := testTxPoolConfig + config.Lifetime = time.Second + config.NoLocals = nolocals + + pool := NewTxPool(config, params.TestChainConfig, 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(pricedTransaction(1, 100000, big.NewInt(1), local)); 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 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. +func TestTransactionPendingLimiting(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + 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(transaction(i, 100000, key)); 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 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. +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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.GlobalSlots = config.AccountSlots * 10 + + pool := NewTxPool(config, params.TestChainConfig, 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.GlobalSlots)/len(keys)*2; j++ { + txs = append(txs, transaction(nonces[addr], 100000, key)) + nonces[addr]++ + } + } + // 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) + } +} + +func TestTransactionPendingGlobalLimitingEIP1559(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.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.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) + + 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) + } +} + +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)) + } + // 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]++ + } + } + // 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) + } +} + +// Tests that if transactions start being capped, transactions are also removed from 'all' +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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.AccountSlots = 2 + config.AccountQueue = 2 + config.GlobalSlots = 8 + + pool := NewTxPool(config, params.TestChainConfig, 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, transaction(uint64(j), 100000, key)) + } + // 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 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. +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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + config := testTxPoolConfig + config.GlobalSlots = 1 + + pool := NewTxPool(config, params.TestChainConfig, 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, transaction(nonces[addr], 100000, key)) + 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 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. +// +// Note, local transactions are never allowed to be dropped. +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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, 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(2), keys[0])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0])) + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[1])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[1])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[1])) + + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[2])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) + txs = append(txs, pricedTransaction(3, 100000, big.NewInt(2), 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(2)) + + 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(1), keys[1])); err != ErrUnderpriced { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + } + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); 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(2), keys[0])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); 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 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())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} + + pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, 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)), keys[2]) + if err := pool.AddLocal(pendingTx); err != nil { + t.Fatal(err) + } + // Add queued transaction. + queuedTx := pricedTransaction(i+501, 100000, big.NewInt(int64(i)), keys[2]) + 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 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 testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { +func TestTransactionPoolRepricingKeepsLocalsEIP1559Finalized(t *testing.T) { t.Parallel() - // Create the pool to test the limit enforcement with + // 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)} - - 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) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := NewTxPool(testTxPoolConfig, 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) + // 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(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, transaction(nonces[addr]+1, 100000, key)) - nonces[addr]++ + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000*1000000)) } - // 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) + // 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) } - 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() + // 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) } - if queued > int(config.GlobalQueue) { - t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + 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) } - } 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) + if queued != expQueued { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) } - // 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) + + 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 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. +// 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. // -// 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, nolocals bool) { - // Reduce the eviction interval to a testable amount - defer func(old time.Duration) { evictionInterval = old }(evictionInterval) - evictionInterval = time.Second +// Note, local transactions are never allowed to be dropped. +func TestTransactionPoolUnderpricing(t *testing.T) { + t.Parallel() - // Create the pool to test the non-expiration enforcement + // 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)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig - config.Lifetime = time.Second - config.NoLocals = nolocals + config.GlobalSlots = 2 + config.GlobalQueue = 2 pool := NewTxPool(config, params.TestChainConfig, 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)) + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() - // Add the two transactions and ensure they both are queued up - if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); 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) + // 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, pricedTransaction(1, 100000, big.NewInt(2), keys[0])) + + 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 != 0 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + 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) } - // Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains - time.Sleep(2 * config.Lifetime) - + // 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(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); 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 != 0 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } - 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 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) } -} - -// 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. -func TestTransactionPendingLimiting(t *testing.T) { - t.Parallel() - - // Create a test account and fund it - pool, key := setupTxPool() - 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(transaction(i, 100000, key)); 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) - } + // 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 = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) } - if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { - t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { - t.Fatalf("event firing failed: %v", err) + 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) } } -// Tests that if the transaction count belonging to multiple accounts go above -// some hard threshold, the higher transactions are dropped to prevent DOS -// attacks. -func TestTransactionPendingGlobalLimiting(t *testing.T) { +func TestTransactionPoolUnderpricingEIP1559(t *testing.T) { t.Parallel() - // Create the pool to test the limit enforcement with + // 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)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} config := testTxPoolConfig - config.GlobalSlots = config.AccountSlots * 10 + config.GlobalSlots = 2 + config.GlobalQueue = 2 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + 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, 5) + 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 - nonces := make(map[common.Address]uint64) - + // Generate and queue a batch of transactions, both pending and queued 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, transaction(nonces[addr], 100000, key)) - nonces[addr]++ - } - } - // Import the batch and verify that limits have been enforced - pool.AddRemotesSync(txs) - pending := 0 - for _, list := range pool.pending { - pending += list.Len() + 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 pending > int(config.GlobalSlots) { - t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) + 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) } } -// Tests that if transactions start being capped, transactions are also removed from 'all' -func TestTransactionCapClearsFromAll(t *testing.T) { +func TestTransactionPoolUnderpricingEIP1559Finalized(t *testing.T) { t.Parallel() - // Create the pool to test the limit enforcement with + // 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)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} config := testTxPoolConfig - config.AccountSlots = 2 - config.AccountQueue = 2 - config.GlobalSlots = 8 + config.GlobalSlots = 2 + config.GlobalQueue = 2 - pool := NewTxPool(config, params.TestChainConfig, blockchain) + 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)) + // Keep track of transaction events to ensure all executables get announced + events := make(chan NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() - txs := types.Transactions{} - for j := 0; j < int(config.GlobalSlots)*2; j++ { - txs = append(txs, transaction(uint64(j), 100000, key)) + // 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)) } - // Import the batch and verify that limits have been enforced + // 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) } } -// 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. -func TestTransactionPendingMinimumAllowance(t *testing.T) { +// 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. +func TestTransactionPoolStableUnderpricing(t *testing.T) { t.Parallel() - // Create the pool to test the limit enforcement with + // 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)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} config := testTxPoolConfig - config.GlobalSlots = 1 + config.GlobalSlots = 128 + config.GlobalQueue = 0 pool := NewTxPool(config, params.TestChainConfig, 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, 5) + 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)) } - // Generate and queue a batch of transactions - nonces := make(map[common.Address]uint64) - + // Fill up the entire queue with the same transaction price points txs := types.Transactions{} - for _, key := range keys { - addr := crypto.PubkeyToAddress(key.PublicKey) - for j := 0; j < int(config.AccountSlots)*2; j++ { - txs = append(txs, transaction(nonces[addr], 100000, key)) - nonces[addr]++ - } + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) } - // 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) - } + 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) } } -// 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. -// -// Note, local transactions are never allowed to be dropped. -func TestTransactionPoolRepricing(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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + 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 @@ -1101,291 +3952,317 @@ func TestTransactionPoolRepricing(t *testing.T) { defer sub.Unsubscribe() // Create a number of test accounts and fund them - keys := make([]*ecdsa.PrivateKey, 4) + 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)) } - // Generate and queue a batch of transactions, both pending and queued + // Fill up the entire queue with the same transaction price points txs := types.Transactions{} - - txs = append(txs, pricedTransaction(0, 100000, big.NewInt(2), keys[0])) - txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) - txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0])) - - txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[1])) - txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[1])) - txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[1])) - - txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[2])) - txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) - txs = append(txs, pricedTransaction(3, 100000, big.NewInt(2), keys[2])) - - ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3]) - - // Import the batch and that both pending and queued transactions match up + 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) - pool.AddLocal(ltx) pending, queued := pool.Stats() - if pending != 7 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } - if queued != 3 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, 7); err != nil { + 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) } - // Reprice the pool and check that underpriced transactions get dropped - pool.SetGasPrice(big.NewInt(2)) - + // 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 != 2 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) } - if queued != 5 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, 0); err != nil { - t.Fatalf("reprice event firing failed: %v", err) + 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) } - // 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(1), keys[1])); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) - } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); 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) +} + +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)) } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) + // 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))) } - // 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) + 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 pending, _ = pool.Stats(); 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, 1); err != nil { - t.Fatalf("post-reprice local event firing failed: %v", err) + 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) } - // And we can fill gaps with properly priced transactions - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[0])); err != nil { - t.Fatalf("failed to add pending transaction: %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) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { - t.Fatalf("failed to add pending 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 err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil { - t.Fatalf("failed to add queued transaction: %v", err) + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) } - if err := validateEvents(events, 5); err != nil { - t.Fatalf("post-reprice event firing failed: %v", err) + 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 setting the transaction pool gas price to a higher value does not -// remove local transactions. -func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { +// 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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, 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 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] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key) } - // 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)), keys[2]) - if err := pool.AddLocal(pendingTx); err != nil { - t.Fatal(err) - } - // Add queued transaction. - queuedTx := pricedTransaction(i+501, 100000, big.NewInt(int64(i)), keys[2]) - if err := pool.AddLocal(queuedTx); err != nil { - t.Fatal(err) + 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() - 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 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 err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) + if i%2 == 1 && err != nil { + t.Errorf("add %d failed: %v", i, 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. -// -// Note, local transactions are never allowed to be dropped. -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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} - - config := testTxPoolConfig - config.GlobalSlots = 2 - config.GlobalQueue = 2 - - pool := NewTxPool(config, params.TestChainConfig, 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)) + pending, queued = pool.Stats() + if pending != len(txs) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) } - // Generate and queue a batch of transactions, both pending and queued - txs := types.Transactions{} + 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) + } +} - txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[0])) - txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[0])) +func TestTransactionDeduplicationEIP1559(t *testing.T) { + t.Parallel() - txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[1])) + // 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)} - ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[2]) + pool := NewTxPool(testTxPoolConfig, params.EIP1559ChainConfig, blockchain) + defer pool.Stop() - // Import the batch and that both pending and queued transactions match up - pool.AddRemotes(txs) - pool.AddLocal(ltx) + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) - pending, queued := pool.Stats() - if pending != 3 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + // 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)) } - if queued != 1 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + var firsts []*types.Transaction + for i := 0; i < len(txs); i += 2 { + firsts = append(firsts, txs[i]) } - if err := validateEvents(events, 3); err != nil { - t.Fatalf("original event firing failed: %v", err) + errs := pool.AddRemotesSync(firsts) + if len(errs) != len(firsts) { + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) + for i, err := range errs { + if err != nil { + t.Errorf("add %d failed: %v", i, 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) + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) } - // 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 queued != len(txs)/2-1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); 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) + // 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)) } - 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) + 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 != 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 pending != len(txs) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) } - if err := validateEvents(events, 1); err != nil { - t.Fatalf("additional event firing failed: %v", err) + 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) } - // 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) +} + +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)) } - ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) - if err := pool.AddLocal(ltx); err != nil { - t.Fatalf("failed to add new underpriced local transaction: %v", err) + var firsts []*types.Transaction + for i := 0; i < len(txs); i += 2 { + firsts = append(firsts, txs[i]) } - pending, queued = pool.Stats() - if pending != 3 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + errs := pool.AddRemotesSync(firsts) + if len(errs) != len(firsts) { + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) } - if queued != 1 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + for i, err := range errs { + if err != nil { + t.Errorf("add %d failed: %v", i, err) + } } - if err := validateEvents(events, 2); err != nil { - t.Fatalf("local event firing failed: %v", 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 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. -func TestTransactionPoolStableUnderpricing(t *testing.T) { +// Tests that the pool rejects replacement transactions that don't meet the minimum +// price bump required. +func TestTransactionReplacement(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)} - - config := testTxPoolConfig - config.GlobalSlots = 128 - config.GlobalQueue = 0 + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1393,126 +4270,155 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { 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)) + // 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(1), key)); err != nil { + t.Fatalf("failed to add original cheap pending transaction: %v", err) } - // 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, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) + if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + } + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); 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) } - 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 err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil { + t.Fatalf("failed to add original proper pending transaction: %v", err) } - if queued != 0 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced { + t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) } - if err := validateEvents(events, int(config.GlobalSlots)); err != nil { - t.Fatalf("original event firing failed: %v", err) + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil { + t.Fatalf("failed to replace original proper pending transaction: %v", err) } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) + if err := validateEvents(events, 2); err != nil { + t.Fatalf("proper replacement event firing failed: %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) + + // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { + t.Fatalf("failed to add original cheap queued 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 err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { + t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) } - if queued != 0 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { + t.Fatalf("failed to replace original cheap queued transaction: %v", err) } - if err := validateEvents(events, 1); err != nil { - t.Fatalf("additional event firing failed: %v", err) + + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil { + t.Fatalf("failed to add original proper queued transaction: %v", err) + } + if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), 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), key)); 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 the pool rejects duplicate transactions. -func TestTransactionDeduplication(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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + 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)) - // 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] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key) + // 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) } - var firsts []*types.Transaction - for i := 0; i < len(txs); i += 2 { - firsts = append(firsts, txs[i]) + 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) } - errs := pool.AddRemotesSync(firsts) - if len(errs) != len(firsts) { - t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) + 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) } - for i, err := range errs { - if err != nil { - t.Errorf("add %d failed: %v", i, err) - } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("proper replacement event firing failed: %v", err) } - pending, queued := pool.Stats() - if pending != 1 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + + // 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 queued != len(txs)/2-1 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) + 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) } - // 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)) + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(3), key)); err != nil { + t.Fatalf("failed to replace original cheap queued transaction: %v", err) } - 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) - } + + 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) } - pending, queued = pool.Stats() - if pending != len(txs) { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) + 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 queued != 0 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + 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) } } -// Tests that the pool rejects replacement transactions that don't meet the minimum -// price bump required. -func TestTransactionReplacement(t *testing.T) { +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())) - blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := NewTxPool(testTxPoolConfig, params.EIP1559FinalizedChainConfig, blockchain) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1528,26 +4434,26 @@ func TestTransactionReplacement(t *testing.T) { price := int64(100) threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100 - if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil { + 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(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { + 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(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { + 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), key)); err != nil { + 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(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced { + 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(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil { + 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 { @@ -1555,40 +4461,268 @@ func TestTransactionReplacement(t *testing.T) { } // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { + 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(1), key)); err != ErrReplaceUnderpriced { + 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(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { + 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(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil { + 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-1), key)); err != ErrReplaceUnderpriced { - t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + 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) } +func TestTransactionJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) } + +func testTransactionJournaling(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), nil} + + config := testTxPoolConfig + config.NoLocals = nolocals + config.Journal = journal + config.Rejournal = time.Second + + pool := NewTxPool(config, params.TestChainConfig, 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(pricedTransaction(1, 100000, big.NewInt(1), local)); 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(pricedTransaction(0, 100000, big.NewInt(1), remote)); 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), nil} + + pool = NewTxPool(config, params.TestChainConfig, 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), nil} + pool = NewTxPool(config, params.TestChainConfig, 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 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 := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil { - t.Fatalf("failed to replace original proper queued transaction: %v", err) + + 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() - if err := validateEvents(events, 0); err != nil { - t.Fatalf("queued replacement event firing failed: %v", err) + 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() } -// Tests that local transactions are journaled to disk, but remote transactions -// get discarded between restarts. -func TestTransactionJournaling(t *testing.T) { testTransactionJournaling(t, false) } -func TestTransactionJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) } +func TestTransactionJournalingEIP1559Finalized(t *testing.T) { + testTransactionJournalingEIP1559Finalized(t, false) +} +func TestTransactionJournalingNoLocalsEIP1559Finalized(t *testing.T) { + testTransactionJournalingEIP1559Finalized(t, true) +} -func testTransactionJournaling(t *testing.T, nolocals bool) { +func testTransactionJournalingEIP1559Finalized(t *testing.T, nolocals bool) { t.Parallel() // Create a temporary file for the journal @@ -1605,14 +4739,14 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { // 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)} + 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.TestChainConfig, blockchain) + pool := NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) // Create two test accounts to ensure remotes expire but locals do not local, _ := crypto.GenerateKey() @@ -1622,16 +4756,16 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { 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 { + 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(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { + 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 { + 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(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil { + 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() @@ -1647,9 +4781,9 @@ 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), big.NewInt(1)} - pool = NewTxPool(config, params.TestChainConfig, blockchain) + pool = NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) pending, queued = pool.Stats() if queued != 0 { @@ -1664,6 +4798,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) } @@ -1674,8 +4809,8 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { pool.Stop() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = &testBlockChain{statedb, 1000000, new(event.Feed)} - pool = NewTxPool(config, params.TestChainConfig, blockchain) + blockchain = &testBlockChain{statedb, 1000000, new(event.Feed), big.NewInt(1)} + pool = NewTxPool(config, params.EIP1559FinalizedChainConfig, blockchain) pending, queued = pool.Stats() if pending != 0 { @@ -1703,7 +4838,7 @@ func TestTransactionStatusCheck(t *testing.T) { // 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)} + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed), nil} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) defer pool.Stop() @@ -1752,6 +4887,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]) + } + } +} + // Benchmarks the speed of validating the contents of the pending queue of the // transaction pool. func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } @@ -1777,6 +5020,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) } @@ -1802,6 +5102,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) } @@ -1829,29 +5186,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) + } } From 4f0eaae822254c5f8000062b054ed1aa304b602c Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Wed, 11 Dec 2019 16:13:37 -0600 Subject: [PATCH 09/13] fix trx decoding; test --- core/types/transaction.go | 4 ++-- core/types/transaction_test.go | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index 23a833ab6a35..651d12c14222 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,7 +45,7 @@ type Transaction struct { type txdata struct { AccountNonce uint64 `json:"nonce" gencodec:"required"` - Price *big.Int `json:"gasPrice"` + Price *big.Int `json:"gasPrice" rlp:"nil"` GasLimit uint64 `json:"gas" gencodec:"required"` Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation Amount *big.Int `json:"value" gencodec:"required"` @@ -244,7 +244,7 @@ func (tx *Transaction) DecodeRLP(stream *rlp.Stream) error { } tx.data = txdata{ AccountNonce: *accountNonce, - Price: price, + Price: nil, GasLimit: *gasLimit, Recipient: recipient, Amount: amount, diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 56412b15eb49..747644292f75 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -147,6 +147,49 @@ func TestEIP1159TransactionEncode(t *testing.T) { } } +func TestEIP1159TransactionDecode(t *testing.T) { + tx, err := decodeTx(common.Hex2Bytes("f86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3")) + if err != nil { + t.Fatal(err) + } + if tx.data.FeeCap == nil || tx.data.FeeCap.Cmp(eip1559Tx.data.FeeCap) != 0 { + t.Fatal("unexpected FeeCap") + } + if tx.data.GasPremium == nil || tx.data.GasPremium.Cmp(eip1559Tx.data.GasPremium) != 0 { + t.Fatal("unexpected GasPremium") + } + if tx.data.Price != nil { + t.Fatal("expected GasPrice to be nil") + } + if tx.data.Hash != nil { + t.Fatal("expected tx Hash to be nil") + } + if tx.data.GasLimit != eip1559Tx.data.GasLimit { + t.Fatal("unexpected GasLimit") + } + if tx.data.Amount == nil || tx.data.Amount.Cmp(eip1559Tx.data.Amount) != 0 { + t.Fatal("unexpected Amount") + } + if tx.data.Recipient == nil || !bytes.Equal(tx.data.Recipient.Bytes(), eip1559Tx.data.Recipient.Bytes()) { + t.Fatal("unexpected Recipient") + } + if !bytes.Equal(tx.data.Payload, eip1559Tx.data.Payload) { + t.Fatal("unexpected Payload") + } + if tx.data.AccountNonce != eip1559Tx.data.AccountNonce { + t.Fatal("unexpected AccountNonce") + } + if tx.data.R == nil || tx.data.R.Cmp(eip1559Tx.data.R) != 0 { + t.Fatal("unexpected R") + } + if tx.data.V == nil || tx.data.V.Cmp(eip1559Tx.data.V) != 0 { + t.Fatal("unexpected V") + } + if tx.data.S == nil || tx.data.S.Cmp(eip1559Tx.data.S) != 0 { + t.Fatal("unexpected S") + } +} + func decodeTx(data []byte) (*Transaction, error) { var tx Transaction t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) From f4a7c08112edd2ed21f509a5f2a7402652e2d946 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Thu, 12 Dec 2019 22:36:53 -0600 Subject: [PATCH 10/13] block_validator_test and fixes --- core/block_validator.go | 23 +- core/block_validator_test.go | 504 +++++++++++++++++++++++++++++++++-- core/chain_makers_test.go | 18 +- 3 files changed, 500 insertions(+), 45 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 177e3285eebb..af8a77c92899 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -152,10 +151,6 @@ func CalcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block, gas // Start at 50 : 50 and then shift to 0 : 100 // The GasLimit for the legacy pool is (params.MaxGasEIP1559 - EIP1559GasLimit) func calcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block) (uint64, *big.Int) { - // Panic if we do not have a block number set for EIP1559 activation - if config.EIP1559Block == nil { - panic("chain config is missing EIP1559Block") - } height := new(big.Int).Add(parent.Number(), common.Big1) // If we are at the block of EIP1559 activation then the BaseFee is set to the initial value @@ -164,12 +159,9 @@ func calcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block) (ui return params.MaxGasEIP1559 / 2, new(big.Int).SetUint64(params.EIP1559InitialBaseFee) } - /* - Otherwise, calculate the BaseFee - As a default strategy, miners set BASEFEE as follows. Let delta = block.gas_used - TARGET_GASUSED (possibly negative). - Set BASEFEE = PARENT_BASEFEE + PARENT_BASEFEE * delta // TARGET_GASUSED // BASEFEE_MAX_CHANGE_DENOMINATOR, - clamping this result inside of the allowable bounds if needed (with the parameter setting above clamping will not be required). - */ + // Otherwise, calculate the BaseFee + // As a default strategy, miners set BASEFEE as follows. Let delta = block.gas_used - TARGET_GASUSED (possibly negative). + // Set BASEFEE = PARENT_BASEFEE + PARENT_BASEFEE * delta // TARGET_GASUSED // BASEFEE_MAX_CHANGE_DENOMINATOR, delta := new(big.Int).Sub(new(big.Int).SetUint64(parent.GasUsed()), new(big.Int).SetUint64(params.TargetGasUsed)) mul := new(big.Int).Mul(parent.BaseFee(), delta) @@ -177,18 +169,23 @@ func calcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block) (ui div2 := new(big.Int).Div(div, new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator)) baseFee := new(big.Int).Add(parent.BaseFee(), div2) - // Panic if the BaseFee is not valid // A valid BASEFEE is one such that abs(BASEFEE - PARENT_BASEFEE) <= max(1, PARENT_BASEFEE // BASEFEE_MAX_CHANGE_DENOMINATOR) diff := new(big.Int).Sub(baseFee, parent.BaseFee()) + neg := false if diff.Sign() < 0 { + neg = true diff.Neg(diff) } max := new(big.Int).Div(parent.BaseFee(), new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator)) if max.Cmp(common.Big1) < 0 { max = common.Big1 } + // If derived BaseFee is not valid, restrict it within the bounds if diff.Cmp(max) > 0 { - panic("invalid BaseFee") + if neg { + max.Neg(max) + } + baseFee.Set(new(big.Int).Add(parent.BaseFee(), max)) } // If EIP1559 is finalized, our limit for the EIP1559 pool is the entire max limit diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 9fed8223ed77..dd5ba9e16647 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -17,6 +17,7 @@ package core import ( + "math/big" "runtime" "testing" "time" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) @@ -76,6 +78,172 @@ func TestHeaderVerification(t *testing.T) { } } +func TestHeaderVerificationEIP1559(t *testing.T) { + // Create a simple chain to verify + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + testdb = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.EIP1559ChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee)} + genesis = gspec.MustCommit(testdb) + signer = types.HomesteadSigner{} + blocks, _ = GenerateChain(params.EIP1559ChainConfig, genesis, ethash.NewFaker(), testdb, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + ) + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces + chain, _ := NewBlockChain(testdb, nil, params.EIP1559ChainConfig, ethash.NewFaker(), vm.Config{}, nil) + defer chain.Stop() + + for i := 0; i < len(blocks); i++ { + for j, valid := range []bool{true, false} { + var results <-chan error + + if valid { + engine := ethash.NewFaker() + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + } else { + engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + } + // Wait for the verification result + select { + case result := <-results: + if (result == nil) != valid { + t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, result, valid) + } + case <-time.After(time.Second): + t.Fatalf("test %d.%d: verification timeout", i, j) + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("test %d.%d: unexpected result returned: %v", i, j, result) + case <-time.After(25 * time.Millisecond): + } + } + chain.InsertChain(blocks[i : i+1]) + } +} + +func TestHeaderVerificationEIP1559Finalized(t *testing.T) { + // Create a simple chain to verify + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + testdb = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.EIP1559FinalizedChainConfig, + Alloc: GenesisAlloc{addr1: {Balance: new(big.Int).SetUint64((params.EIP1559InitialBaseFee * params.TxGas) + 1000000)}}, + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee)} + genesis = gspec.MustCommit(testdb) + signer = types.HomesteadSigner{} + blocks, _ = GenerateChain(params.EIP1559FinalizedChainConfig, genesis, ethash.NewFaker(), testdb, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil, new(big.Int), new(big.Int).SetUint64(params.EIP1559InitialBaseFee)), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 attempts to pass it on to addr3 using a EIP1559 transaction + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + ) + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces + chain, _ := NewBlockChain(testdb, nil, params.EIP1559FinalizedChainConfig, ethash.NewFaker(), vm.Config{}, nil) + defer chain.Stop() + + for i := 0; i < len(blocks); i++ { + for j, valid := range []bool{true, false} { + var results <-chan error + + if valid { + engine := ethash.NewFaker() + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + } else { + engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + } + // Wait for the verification result + select { + case result := <-results: + if (result == nil) != valid { + t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, result, valid) + } + case <-time.After(time.Second): + t.Fatalf("test %d.%d: verification timeout", i, j) + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("test %d.%d: unexpected result returned: %v", i, j, result) + case <-time.After(25 * time.Millisecond): + } + } + chain.InsertChain(blocks[i : i+1]) + } +} + // Tests that concurrent header verification works, for both good and bad blocks. func TestHeaderConcurrentVerification2(t *testing.T) { testHeaderConcurrentVerification(t, 2) } func TestHeaderConcurrentVerification8(t *testing.T) { testHeaderConcurrentVerification(t, 8) } @@ -147,6 +315,158 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { } } +func TestHeaderConcurrentVerificationEIP15592(t *testing.T) { + testHeaderConcurrentVerificationEIP1559(t, 2) +} +func TestHeaderConcurrentVerificationEIP15598(t *testing.T) { + testHeaderConcurrentVerificationEIP1559(t, 8) +} +func TestHeaderConcurrentVerificationEIP155932(t *testing.T) { + testHeaderConcurrentVerificationEIP1559(t, 32) +} + +func testHeaderConcurrentVerificationEIP1559(t *testing.T, threads int) { + // Create a simple chain to verify + var ( + testdb = rawdb.NewMemoryDatabase() + gspec = &Genesis{Config: params.EIP1559ChainConfig, BaseFee: new(big.Int)} + genesis = gspec.MustCommit(testdb) + blocks, _ = GenerateChain(params.EIP1559ChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil) + ) + headers := make([]*types.Header, len(blocks)) + seals := make([]bool, len(blocks)) + + for i, block := range blocks { + headers[i] = block.Header() + seals[i] = true + } + // Set the number of threads to verify on + old := runtime.GOMAXPROCS(threads) + defer runtime.GOMAXPROCS(old) + + // Run the header checker for the entire block chain at once both for a valid and + // also an invalid chain (enough if one arbitrary block is invalid). + for i, valid := range []bool{true, false} { + var results <-chan error + + if valid { + chain, _ := NewBlockChain(testdb, nil, params.EIP1559ChainConfig, ethash.NewFaker(), vm.Config{}, nil) + _, results = chain.engine.VerifyHeaders(chain, headers, seals) + chain.Stop() + } else { + chain, _ := NewBlockChain(testdb, nil, params.EIP1559ChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil) + _, results = chain.engine.VerifyHeaders(chain, headers, seals) + chain.Stop() + } + // Wait for all the verification results + checks := make(map[int]error) + for j := 0; j < len(blocks); j++ { + select { + case result := <-results: + checks[j] = result + + case <-time.After(time.Second): + t.Fatalf("test %d.%d: verification timeout", i, j) + } + } + // Check nonce check validity + for j := 0; j < len(blocks); j++ { + want := valid || (j < len(blocks)-2) // We chose the last-but-one nonce in the chain to fail + if (checks[j] == nil) != want { + t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, checks[j], want) + } + if !want { + // A few blocks after the first error may pass verification due to concurrent + // workers. We don't care about those in this test, just that the correct block + // errors out. + break + } + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("test %d: unexpected result returned: %v", i, result) + case <-time.After(25 * time.Millisecond): + } + } +} + +func TestHeaderConcurrentVerificationEIP1559Finalized2(t *testing.T) { + testHeaderConcurrentVerificationEIP1559Finalized(t, 2) +} +func TestHeaderConcurrentVerificationEIP1559Finalized8(t *testing.T) { + testHeaderConcurrentVerificationEIP1559Finalized(t, 8) +} +func TestHeaderConcurrentVerificationEIP1559Finalized32(t *testing.T) { + testHeaderConcurrentVerificationEIP1559Finalized(t, 32) +} + +func testHeaderConcurrentVerificationEIP1559Finalized(t *testing.T, threads int) { + // Create a simple chain to verify + var ( + testdb = rawdb.NewMemoryDatabase() + gspec = &Genesis{Config: params.EIP1559FinalizedChainConfig, BaseFee: new(big.Int)} + genesis = gspec.MustCommit(testdb) + blocks, _ = GenerateChain(params.EIP1559FinalizedChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil) + ) + headers := make([]*types.Header, len(blocks)) + seals := make([]bool, len(blocks)) + + for i, block := range blocks { + headers[i] = block.Header() + seals[i] = true + } + // Set the number of threads to verify on + old := runtime.GOMAXPROCS(threads) + defer runtime.GOMAXPROCS(old) + + // Run the header checker for the entire block chain at once both for a valid and + // also an invalid chain (enough if one arbitrary block is invalid). + for i, valid := range []bool{true, false} { + var results <-chan error + + if valid { + chain, _ := NewBlockChain(testdb, nil, params.EIP1559FinalizedChainConfig, ethash.NewFaker(), vm.Config{}, nil) + _, results = chain.engine.VerifyHeaders(chain, headers, seals) + chain.Stop() + } else { + chain, _ := NewBlockChain(testdb, nil, params.EIP1559FinalizedChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil) + _, results = chain.engine.VerifyHeaders(chain, headers, seals) + chain.Stop() + } + // Wait for all the verification results + checks := make(map[int]error) + for j := 0; j < len(blocks); j++ { + select { + case result := <-results: + checks[j] = result + + case <-time.After(time.Second): + t.Fatalf("test %d.%d: verification timeout", i, j) + } + } + // Check nonce check validity + for j := 0; j < len(blocks); j++ { + want := valid || (j < len(blocks)-2) // We chose the last-but-one nonce in the chain to fail + if (checks[j] == nil) != want { + t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, checks[j], want) + } + if !want { + // A few blocks after the first error may pass verification due to concurrent + // workers. We don't care about those in this test, just that the correct block + // errors out. + break + } + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("test %d: unexpected result returned: %v", i, result) + case <-time.After(25 * time.Millisecond): + } + } +} + // Tests that aborting a header validation indeed prevents further checks from being // run, as well as checks that no left-over goroutines are leaked. func TestHeaderConcurrentAbortion2(t *testing.T) { testHeaderConcurrentAbortion(t, 2) } @@ -198,27 +518,165 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { } } -/* - We need to test: - - 1. That the correct, original, gasLimit is returned in the period before activation and no BaseFee is returned - 2. That both the correct EIP1559 gasLimit and BaseFee are calculated and returned in the period between activation and finalization - 3. That the entire `MaxGasEIP1559` is returned as the EIP1559 gasLimit after finalization and the correct BaseFee is calculated - -*/ - -// TestEIP1559Verification tests that verificaiton works after EIP1559 initialization and finalization -func TestEIP1559Verification(t *testing.T) { - //testEIP1559Verification(t) - //testEIP1559VerificationAfterFinalization(t) -} - -// testEIP1559Verification tests verification during the EIP1559 transition phase -func testEIP1559Verification(t *testing.T) { - panic("implement me") -} - -// testEIP1559VerificationAfterFinalization tests verification after EIP1559 finalization -func testEIP1559VerificationAfterFinalization(t *testing.T) { - panic("implement me") +// TestCalcGasLimitAndBaseFee tests that CalcGasLimitAndBaseFee() returns the correct values +func TestCalcGasLimitAndBaseFee(t *testing.T) { + testConditions := []struct { + // Test inputs + config *params.ChainConfig + eip1559Block *big.Int + eip1559FinalizedBlock *big.Int + parentGasLimit uint64 + parentGasUsed uint64 + parentBaseFee *big.Int + parentBlockNumber *big.Int + // Expected results + gasLimit uint64 + baseFee *big.Int + }{ + { + // Before activation GasLimit is calculated using the legacy function and BaseFee is nil + params.TestChainConfig, + nil, + nil, + 8000000, + 8000000, + nil, + big.NewInt(5), + 8000000, + nil, + }, { + // At the EIP1559 initialization block the GasLimit is split evenly between the two pools and BaseFee is the initial value + params.EIP1559ChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + nil, + 8000000, + 8000000, + big.NewInt(1100000000), + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber - 1), + params.MaxGasEIP1559 / 2, + new(big.Int).SetUint64(params.EIP1559InitialBaseFee), + }, + // After initialization the GasLimit and BaseFee are set according to their functions + // Half way between initialization and finalization we should be at a 25 : 75 legacy : eip1559 split + { + params.EIP1559ChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + nil, + 8000000, + 8000000, + new(big.Int).SetUint64(params.EIP1559InitialBaseFee), + new(big.Int).SetUint64((params.EIP1559ForkBlockNumber + (params.EIP1559ForkFinalizedBlockNumber-params.EIP1559ForkBlockNumber)/2) - 1), + (params.MaxGasEIP1559 * 3) / 4, + new(big.Int).SetUint64(params.EIP1559InitialBaseFee), + }, + { + params.EIP1559ChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + nil, + 8000000, + 7000000, + new(big.Int).SetUint64(params.EIP1559InitialBaseFee), + new(big.Int).SetUint64((params.EIP1559ForkBlockNumber + (params.EIP1559ForkFinalizedBlockNumber-params.EIP1559ForkBlockNumber)/2) - 1), + (params.MaxGasEIP1559 * 3) / 4, + new(big.Int).SetUint64(984375000), + }, + { + params.EIP1559ChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + nil, + 8000000, + 8000000, + big.NewInt(1100000000), + new(big.Int).SetUint64((params.EIP1559ForkBlockNumber + (params.EIP1559ForkFinalizedBlockNumber-params.EIP1559ForkBlockNumber)/2) - 1), + (params.MaxGasEIP1559 * 3) / 4, + new(big.Int).SetUint64(1100000000), + }, + { + params.EIP1559ChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + nil, + 8000000, + 7000000, + big.NewInt(1100000000), + new(big.Int).SetUint64((params.EIP1559ForkBlockNumber + (params.EIP1559ForkFinalizedBlockNumber-params.EIP1559ForkBlockNumber)/2) - 1), + (params.MaxGasEIP1559 * 3) / 4, + new(big.Int).SetUint64(1082812500), + }, + // At and beyond EIP1559 finalization the GasLimit (for the EIP1559 pool) is the entire MaxGasEIP1559 + { + params.EIP1559FinalizedChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber), + 8000000, + 7000000, + big.NewInt(1082812500), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber - 1), + params.MaxGasEIP1559, + new(big.Int).SetUint64(1065893554), + }, + { + params.EIP1559FinalizedChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber), + 8000000, + 7000000, + big.NewInt(1065893554), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber + 1), + params.MaxGasEIP1559, + new(big.Int).SetUint64(1049238967), + }, + { + params.EIP1559FinalizedChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber), + 8000000, + params.TargetGasUsed + 1000, + big.NewInt(1049238967), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber + 10000), + params.MaxGasEIP1559, + new(big.Int).SetUint64(1049255361), + }, + { + params.EIP1559FinalizedChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber), + 8000000, + params.MaxGasEIP1559, + big.NewInt(1049238967), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber + 10000), + params.MaxGasEIP1559, + new(big.Int).SetUint64(1180393837), + }, + { + params.EIP1559FinalizedChainConfig, + new(big.Int).SetUint64(params.EIP1559ForkBlockNumber), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber), + 8000000, + 0, + big.NewInt(1049238967), + new(big.Int).SetUint64(params.EIP1559ForkFinalizedBlockNumber + 10000), + params.MaxGasEIP1559, + new(big.Int).SetUint64(918084097), + }, + } + for i, test := range testConditions { + config := *test.config + config.EIP1559Block = test.eip1559Block + config.EIP1559FinalizedBlock = test.eip1559FinalizedBlock + parentHeader := &types.Header{} + parentHeader.GasLimit = test.parentGasLimit + parentHeader.GasUsed = test.parentGasUsed + parentHeader.BaseFee = test.parentBaseFee + parentHeader.Number = test.parentBlockNumber + parentBlock := types.NewBlockWithHeader(parentHeader) + gasLimit, baseFee := CalcGasLimitAndBaseFee(&config, parentBlock, parentHeader.GasLimit, parentHeader.GasLimit) + if gasLimit != test.gasLimit { + t.Errorf("test %d expected GasLimit %d got %d", i+1, test.gasLimit, gasLimit) + } + if baseFee == nil && test.baseFee != nil { + t.Errorf("test %d expected BaseFee %d got nil", i+1, test.baseFee) + } else if baseFee != nil && baseFee.Cmp(test.baseFee) != 0 { + t.Errorf("test %d expected BaseFee %d got %d", i+1, test.baseFee.Uint64(), baseFee.Uint64()) + } + } } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 524db2eb5ab6..94f4b3ea72c8 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -132,9 +132,8 @@ func generateChainBeforeActivation(t *testing.T) { // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, - Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, - BaseFee: new(big.Int), + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, + Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, } genesis := gspec.MustCommit(db) @@ -195,7 +194,7 @@ func generateChainDuringTransition(t *testing.T) { gspec := &Genesis{ Config: params.EIP1559ChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, - BaseFee: new(big.Int), + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee), } genesis := gspec.MustCommit(db) @@ -258,6 +257,7 @@ func generateChainDuringTransition(t *testing.T) { // generateChainAfterFinalization demonstrates that we panic if we try to make a chain with legacy transactions after EIP1559 finalization func generateChainAfterFinalization(t *testing.T) { + // We expect a panic due to an ErrTxNotEIP1559 error because of the panic at line 119 in chain_makers.go defer func() { if err := recover().(error); err != nil { if err != ErrTxNotEIP1559 { @@ -279,7 +279,7 @@ func generateChainAfterFinalization(t *testing.T) { gspec := &Genesis{ Config: params.EIP1559FinalizedChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, - BaseFee: new(big.Int), + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee), } genesis := gspec.MustCommit(db) @@ -339,8 +339,8 @@ func generateChainAfterFinalization2(t *testing.T) { // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: params.EIP1559FinalizedChainConfig, - Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, - BaseFee: new(big.Int), + Alloc: GenesisAlloc{addr1: {Balance: new(big.Int).SetUint64((params.EIP1559InitialBaseFee * params.TxGas) + 1000000)}}, + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee), } genesis := gspec.MustCommit(db) @@ -352,7 +352,7 @@ func generateChainAfterFinalization2(t *testing.T) { switch i { case 0: // In block 1, addr1 sends addr2 some ether. - tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), signer, key1) + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil, new(big.Int), new(big.Int).SetUint64(params.EIP1559InitialBaseFee)), signer, key1) gen.AddTx(tx) case 1: // In block 2, addr1 sends some more ether to addr2. @@ -388,7 +388,7 @@ func generateChainAfterFinalization2(t *testing.T) { if blockchain.CurrentBlock().Number().Uint64() != 5 { t.Fatalf("expected last block to equal %d got %d", 5, blockchain.CurrentBlock().Number().Uint64()) } - if state.GetBalance(addr1).Uint64() != 989000 { + if state.GetBalance(addr1).Uint64() != 2625000989000 { t.Fatalf("expected balance of addr1 to equal %d got %d", 989000, state.GetBalance(addr1).Uint64()) } if state.GetBalance(addr2).Uint64() != 10000 { From fc7506c57b69fdce74310b422e83c15104f842e8 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Thu, 12 Dec 2019 22:37:16 -0600 Subject: [PATCH 11/13] run gencodec on new genesis struct --- core/gen_genesis.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/gen_genesis.go b/core/gen_genesis.go index bb8ea1d6a239..1dcced1a8cb6 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -15,6 +15,7 @@ import ( var _ = (*genesisSpecMarshaling)(nil) +// MarshalJSON marshals as JSON. func (g Genesis) MarshalJSON() ([]byte, error) { type Genesis struct { Config *params.ChainConfig `json:"config"` @@ -29,6 +30,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { Number math.HexOrDecimal64 `json:"number"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` + BaseFee *big.Int `json:"baseFee"` } var enc Genesis enc.Config = g.Config @@ -48,9 +50,11 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.Number = math.HexOrDecimal64(g.Number) enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash + enc.BaseFee = g.BaseFee return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (g *Genesis) UnmarshalJSON(input []byte) error { type Genesis struct { Config *params.ChainConfig `json:"config"` @@ -65,6 +69,7 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { Number *math.HexOrDecimal64 `json:"number"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"` ParentHash *common.Hash `json:"parentHash"` + BaseFee *big.Int `json:"baseFee"` } var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { @@ -112,5 +117,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { if dec.ParentHash != nil { g.ParentHash = *dec.ParentHash } + if dec.BaseFee != nil { + g.BaseFee = dec.BaseFee + } return nil } From 647c8badc17fb4fa6e49ba51da79bf03a5adaba9 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 13 Dec 2019 00:36:47 -0600 Subject: [PATCH 12/13] worker_test and fixes --- miner/worker.go | 14 +- miner/worker_test.go | 349 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 320 insertions(+), 43 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 160a2c170b72..6df55e4d01fc 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -24,7 +24,7 @@ import ( "sync/atomic" "time" - mapset "github.com/deckarep/golang-set" + "github.com/deckarep/golang-set" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -749,7 +749,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin } if w.current.gp1559 == nil { eip1559GasLimit = w.current.header.GasLimit - w.current.gasPool = new(core.GasPool).AddGas(eip1559GasLimit) + w.current.gp1559 = new(core.GasPool).AddGas(eip1559GasLimit) } } else if w.current.gasPool == nil { // If we are before EIP1559 activation then we use header.GasLimit for the legacy pool legacyGasLimit = w.current.header.GasLimit @@ -1053,7 +1053,15 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st feesWei := new(big.Int) for i, tx := range block.Transactions() { - feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice())) + if tx.GasPrice() != nil { + feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice())) + } else if tx.GasPremium() != nil && tx.FeeCap() != nil { + gasPrice := new(big.Int).Add(block.BaseFee(), tx.GasPremium()) + if gasPrice.Cmp(tx.FeeCap()) > 0 { + gasPrice.Set(tx.FeeCap()) + } + feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), gasPrice)) + } } feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether))) diff --git a/miner/worker_test.go b/miner/worker_test.go index 2c8beeaef920..bd03dfeb8721 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -48,9 +48,13 @@ const ( var ( // Test chain configurations - testTxPoolConfig core.TxPoolConfig - ethashChainConfig *params.ChainConfig - cliqueChainConfig *params.ChainConfig + testTxPoolConfig core.TxPoolConfig + ethashChainConfig *params.ChainConfig + cliqueChainConfig *params.ChainConfig + eip1559ChainConfig *params.ChainConfig + eip1559CliqueChainConfig *params.ChainConfig + eip1559FinalizedChainConfig *params.ChainConfig + eip1559FinalizedCliqueChainConfig *params.ChainConfig // Test accounts testBankKey, _ = crypto.GenerateKey() @@ -61,8 +65,12 @@ var ( testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) // Test transactions - pendingTxs []*types.Transaction - newTxs []*types.Transaction + pendingTxs []*types.Transaction + newTxs []*types.Transaction + pendingLegacyAndEIP1559Txs []*types.Transaction + newLegacyAndEIP1559Txs []*types.Transaction + pendingEIP1559Txs []*types.Transaction + newEIP1559Txs []*types.Transaction testConfig = &Config{ Recommit: time.Second, @@ -80,10 +88,28 @@ func init() { Period: 10, Epoch: 30000, } - tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) - pendingTxs = append(pendingTxs, tx1) - tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) - newTxs = append(newTxs, tx2) + eip1559ChainConfig = params.EIP1559ChainConfig + eip1559CliqueChainConfig = params.EIP1559ChainConfig + eip1559CliqueChainConfig.Clique = ¶ms.CliqueConfig{ + Period: 10, + Epoch: 30000, + } + eip1559FinalizedChainConfig = params.EIP1559FinalizedChainConfig + eip1559FinalizedCliqueChainConfig = params.EIP1559FinalizedChainConfig + eip1559FinalizedCliqueChainConfig.Clique = ¶ms.CliqueConfig{ + Period: 10, + Epoch: 30000, + } + tx, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) + pendingTxs = append(pendingTxs, tx) + pendingLegacyAndEIP1559Txs = append(pendingLegacyAndEIP1559Txs, tx) + tx, _ = types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) + newTxs = append(newTxs, tx) + newLegacyAndEIP1559Txs = append(newLegacyAndEIP1559Txs, tx) + tx, _ = types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), types.HomesteadSigner{}, testBankKey) + pendingEIP1559Txs = append(pendingEIP1559Txs, tx) + tx, _ = types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), types.HomesteadSigner{}, testBankKey) + newEIP1559Txs = append(newEIP1559Txs, tx) rand.Seed(time.Now().UnixNano()) } @@ -97,10 +123,11 @@ type testWorkerBackend struct { uncleBlock *types.Block } -func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { +func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int, baseFee *big.Int) *testWorkerBackend { var gspec = core.Genesis{ - Config: chainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, + Config: chainConfig, + Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, + BaseFee: baseFee, } switch e := engine.(type) { @@ -167,19 +194,27 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block { return blocks[0] } -func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { +func (b *testWorkerBackend) newRandomTx(creation, eip1559 bool) *types.Transaction { var tx *types.Transaction if creation { - tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, new(big.Int), common.FromHex(testCode), nil, nil), types.HomesteadSigner{}, testBankKey) + if eip1559 { + tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, nil, common.FromHex(testCode), new(big.Int), new(big.Int)), types.HomesteadSigner{}, testBankKey) + } else { + tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, new(big.Int), common.FromHex(testCode), nil, nil), types.HomesteadSigner{}, testBankKey) + } } else { - tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) + if eip1559 { + tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, nil, nil, new(big.Int), new(big.Int)), types.HomesteadSigner{}, testBankKey) + } else { + tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, new(big.Int), nil, nil, nil), types.HomesteadSigner{}, testBankKey) + } } return tx } -func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { - backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) - backend.txPool.AddLocals(pendingTxs) +func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, baseFee *big.Int, txs []*types.Transaction) (*worker, *testWorkerBackend) { + backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks, baseFee) + backend.txPool.AddLocals(txs) w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil) w.setEtherbase(testBankAddress) return w, backend @@ -208,7 +243,175 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { engine = ethash.NewFaker() } - w, b := newTestWorker(t, chainConfig, engine, db, 0) + w, b := newTestWorker(t, chainConfig, engine, db, 0, nil, pendingTxs) + defer w.close() + + db2 := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil) + defer chain.Stop() + + newBlock := make(chan struct{}) + listenNewBlock := func() { + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + for item := range sub.Chan() { + block := item.Data.(core.NewMinedBlockEvent).Block + _, err := chain.InsertChain([]*types.Block{block}) + if err != nil { + t.Fatalf("Failed to insert new mined block:%d, error:%v", block.NumberU64(), err) + } + newBlock <- struct{}{} + } + } + + // Ensure worker has finished initialization + for { + b := w.pendingBlock() + if b != nil && b.NumberU64() == 1 { + break + } + } + w.start() // Start mining! + + // Ignore first 2 commits caused by start operation + ignored := make(chan struct{}, 2) + w.skipSealHook = func(task *task) bool { + ignored <- struct{}{} + return true + } + for i := 0; i < 2; i++ { + <-ignored + } + + go listenNewBlock() + + // Ignore empty commit here for less noise + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + for i := 0; i < 5; i++ { + b.txPool.AddLocal(b.newRandomTx(true, false)) + b.txPool.AddLocal(b.newRandomTx(false, false)) + b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) + b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) + select { + case <-newBlock: + case <-time.NewTimer(3 * time.Second).C: // Worker needs 1s to include new changes. + t.Fatalf("timeout") + } + } +} + +func TestGenerateBlockAndImportEthashEIP1559(t *testing.T) { + testGenerateBlockAndImportEIP1559(t, false) +} + +func TestGenerateBlockAndImportCliqueEIP1559(t *testing.T) { + testGenerateBlockAndImportEIP1559(t, true) +} + +func testGenerateBlockAndImportEIP1559(t *testing.T, isClique bool) { + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + if isClique { + chainConfig = params.EIP1559ChainConfig + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + } else { + chainConfig = params.EIP1559ChainConfig + engine = ethash.NewFaker() + } + + w, b := newTestWorker(t, chainConfig, engine, db, 0, new(big.Int), pendingLegacyAndEIP1559Txs) + defer w.close() + + db2 := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil) + defer chain.Stop() + + newBlock := make(chan struct{}) + listenNewBlock := func() { + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + for item := range sub.Chan() { + block := item.Data.(core.NewMinedBlockEvent).Block + _, err := chain.InsertChain([]*types.Block{block}) + if err != nil { + t.Fatalf("Failed to insert new mined block:%d, error:%v", block.NumberU64(), err) + } + newBlock <- struct{}{} + } + } + + // Ensure worker has finished initialization + for { + b := w.pendingBlock() + if b != nil && b.NumberU64() == 1 { + break + } + } + w.start() // Start mining! + + // Ignore first 2 commits caused by start operation + ignored := make(chan struct{}, 2) + w.skipSealHook = func(task *task) bool { + ignored <- struct{}{} + return true + } + for i := 0; i < 2; i++ { + <-ignored + } + + go listenNewBlock() + + // Ignore empty commit here for less noise + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + for i := 0; i < 5; i++ { + b.txPool.AddLocal(b.newRandomTx(true, true)) + b.txPool.AddLocal(b.newRandomTx(false, false)) + b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) + b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) + select { + case <-newBlock: + case <-time.NewTimer(3 * time.Second).C: // Worker needs 1s to include new changes. + t.Fatalf("timeout") + } + } +} + +func TestGenerateBlockAndImportEthashEIP1559Finalized(t *testing.T) { + testGenerateBlockAndImportEIP1559Finalized(t, false) +} + +func TestGenerateBlockAndImportCliqueEIP1559Finalized(t *testing.T) { + testGenerateBlockAndImportEIP1559Finalized(t, true) +} + +func testGenerateBlockAndImportEIP1559Finalized(t *testing.T, isClique bool) { + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + if isClique { + chainConfig = params.EIP1559FinalizedChainConfig + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + } else { + chainConfig = params.EIP1559FinalizedChainConfig + engine = ethash.NewFaker() + } + + w, b := newTestWorker(t, chainConfig, engine, db, 0, new(big.Int), pendingEIP1559Txs) defer w.close() db2 := rawdb.NewMemoryDatabase() @@ -257,8 +460,8 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { return len(task.receipts) == 0 } for i := 0; i < 5; i++ { - b.txPool.AddLocal(b.newRandomTx(true)) - b.txPool.AddLocal(b.newRandomTx(false)) + b.txPool.AddLocal(b.newRandomTx(true, true)) + b.txPool.AddLocal(b.newRandomTx(false, true)) b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.newRandomUncle()}}) select { @@ -270,16 +473,28 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { } func TestPendingStateAndBlockEthash(t *testing.T) { - testPendingStateAndBlock(t, ethashChainConfig, ethash.NewFaker()) + testPendingStateAndBlock(t, ethashChainConfig, ethash.NewFaker(), pendingTxs, newTxs, nil) } func TestPendingStateAndBlockClique(t *testing.T) { - testPendingStateAndBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) + testPendingStateAndBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingTxs, newTxs, nil) +} +func TestPendingStateAndBlockEthashEIP1559(t *testing.T) { + testPendingStateAndBlock(t, eip1559ChainConfig, ethash.NewFaker(), pendingLegacyAndEIP1559Txs, newLegacyAndEIP1559Txs, new(big.Int)) +} +func TestPendingStateAndBlockCliqueEIP1559(t *testing.T) { + testPendingStateAndBlock(t, eip1559CliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingLegacyAndEIP1559Txs, newLegacyAndEIP1559Txs, new(big.Int)) +} +func TestPendingStateAndBlockEthashEIP1559Finalized(t *testing.T) { + testPendingStateAndBlock(t, eip1559FinalizedChainConfig, ethash.NewFaker(), pendingEIP1559Txs, newEIP1559Txs, new(big.Int)) +} +func TestPendingStateAndBlockCliqueEIP1559Finalized(t *testing.T) { + testPendingStateAndBlock(t, eip1559FinalizedCliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingEIP1559Txs, newEIP1559Txs, new(big.Int)) } -func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { +func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, pTxs, nTxs []*types.Transaction, baseFee *big.Int) { defer engine.Close() - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, baseFee, pTxs) defer w.close() // Ensure snapshot has been updated. @@ -291,7 +506,7 @@ func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, eng if balance := state.GetBalance(testUserAddress); balance.Cmp(big.NewInt(1000)) != 0 { t.Errorf("account balance mismatch: have %d, want %d", balance, 1000) } - b.txPool.AddLocals(newTxs) + b.txPool.AddLocals(nTxs) // Ensure the new tx events has been processed time.Sleep(100 * time.Millisecond) @@ -302,16 +517,28 @@ func testPendingStateAndBlock(t *testing.T, chainConfig *params.ChainConfig, eng } func TestEmptyWorkEthash(t *testing.T) { - testEmptyWork(t, ethashChainConfig, ethash.NewFaker()) + testEmptyWork(t, ethashChainConfig, ethash.NewFaker(), pendingTxs, nil) } func TestEmptyWorkClique(t *testing.T) { - testEmptyWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) + testEmptyWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingTxs, nil) +} +func TestEmptyWorkEthashEIP1559(t *testing.T) { + testEmptyWork(t, eip1559ChainConfig, ethash.NewFaker(), pendingLegacyAndEIP1559Txs, new(big.Int)) +} +func TestEmptyWorkCliqueEIP1559(t *testing.T) { + testEmptyWork(t, eip1559CliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingLegacyAndEIP1559Txs, new(big.Int)) +} +func TestEmptyWorkEthashEIP1559Finalized(t *testing.T) { + testEmptyWork(t, eip1559FinalizedChainConfig, ethash.NewFaker(), pendingEIP1559Txs, new(big.Int)) +} +func TestEmptyWorkCliqueEIP1559Finalized(t *testing.T) { + testEmptyWork(t, eip1559FinalizedCliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingEIP1559Txs, new(big.Int)) } -func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { +func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, pTxs []*types.Transaction, baseFee *big.Int) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, baseFee, pTxs) defer w.close() var ( @@ -362,10 +589,20 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens } func TestStreamUncleBlock(t *testing.T) { + testStreamUncleBlock(t, ethashChainConfig, pendingTxs, nil) +} +func TestStreamUncleBlockEIP1559(t *testing.T) { + testStreamUncleBlock(t, eip1559ChainConfig, pendingLegacyAndEIP1559Txs, new(big.Int)) +} +func TestStreamUncleBlockEIP1559Finalized(t *testing.T) { + testStreamUncleBlock(t, eip1559FinalizedChainConfig, pendingEIP1559Txs, new(big.Int)) +} + +func testStreamUncleBlock(t *testing.T, chainConfig *params.ChainConfig, pTxs []*types.Transaction, baseFee *big.Int) { ethash := ethash.NewFaker() defer ethash.Close() - w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1) + w, b := newTestWorker(t, chainConfig, ethash, rawdb.NewMemoryDatabase(), 1, baseFee, pTxs) defer w.close() var taskCh = make(chan struct{}) @@ -418,17 +655,33 @@ func TestStreamUncleBlock(t *testing.T) { } func TestRegenerateMiningBlockEthash(t *testing.T) { - testRegenerateMiningBlock(t, ethashChainConfig, ethash.NewFaker()) + testRegenerateMiningBlock(t, ethashChainConfig, ethash.NewFaker(), pendingTxs, newTxs, nil) } func TestRegenerateMiningBlockClique(t *testing.T) { - testRegenerateMiningBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) + testRegenerateMiningBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingTxs, newTxs, nil) +} + +func TestRegenerateMiningBlockEthashEIP1559(t *testing.T) { + testRegenerateMiningBlock(t, eip1559ChainConfig, ethash.NewFaker(), pendingLegacyAndEIP1559Txs, newLegacyAndEIP1559Txs, new(big.Int)) } -func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { +func TestRegenerateMiningBlockCliqueEIP1559(t *testing.T) { + testRegenerateMiningBlock(t, eip1559CliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingLegacyAndEIP1559Txs, newLegacyAndEIP1559Txs, new(big.Int)) +} + +func TestRegenerateMiningBlockEthashEIP1559Finalized(t *testing.T) { + testRegenerateMiningBlock(t, eip1559FinalizedChainConfig, ethash.NewFaker(), pendingEIP1559Txs, newEIP1559Txs, new(big.Int)) +} + +func TestRegenerateMiningBlockCliqueEIP1559Finalized(t *testing.T) { + testRegenerateMiningBlock(t, eip1559FinalizedCliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingEIP1559Txs, newEIP1559Txs, new(big.Int)) +} + +func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, pTxs, nTxs []*types.Transaction, baseFee *big.Int) { defer engine.Close() - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, baseFee, pTxs) defer w.close() var taskCh = make(chan struct{}) @@ -472,7 +725,7 @@ func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, en t.Error("new task timeout") } } - b.txPool.AddLocals(newTxs) + b.txPool.AddLocals(nTxs) time.Sleep(time.Second) select { @@ -483,17 +736,33 @@ func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, en } func TestAdjustIntervalEthash(t *testing.T) { - testAdjustInterval(t, ethashChainConfig, ethash.NewFaker()) + testAdjustInterval(t, ethashChainConfig, ethash.NewFaker(), pendingTxs, nil) } func TestAdjustIntervalClique(t *testing.T) { - testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) + testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingTxs, nil) +} + +func TestAdjustIntervalEthashEIP1559(t *testing.T) { + testAdjustInterval(t, eip1559ChainConfig, ethash.NewFaker(), pendingLegacyAndEIP1559Txs, new(big.Int)) +} + +func TestAdjustIntervalCliqueEIP1559(t *testing.T) { + testAdjustInterval(t, eip1559CliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingLegacyAndEIP1559Txs, new(big.Int)) +} + +func TestAdjustIntervalEthashEIP1559Finalized(t *testing.T) { + testAdjustInterval(t, eip1559FinalizedChainConfig, ethash.NewFaker(), pendingEIP1559Txs, new(big.Int)) +} + +func TestAdjustIntervalCliqueEIP1559Finalized(t *testing.T) { + testAdjustInterval(t, eip1559FinalizedCliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase()), pendingEIP1559Txs, new(big.Int)) } -func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { +func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, pTxs []*types.Transaction, baseFee *big.Int) { defer engine.Close() - w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) + w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, baseFee, pTxs) defer w.close() w.skipSealHook = func(task *task) bool { From 42fcd793774eed370a57c89fcb528803c16f6505 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 13 Dec 2019 07:51:06 -0600 Subject: [PATCH 13/13] blockchain_test --- core/blockchain_test.go | 371 +++++++++++++++++++++++++++------------- core/chain_makers.go | 8 +- 2 files changed, 259 insertions(+), 120 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b472162d810a..f70321fc12b0 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -47,34 +47,37 @@ var ( // newCanonical creates a chain database, and injects a deterministic canonical // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. -func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) { +func newCanonical(engine consensus.Engine, n int, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) (ethdb.Database, *BlockChain, error) { var ( - db = rawdb.NewMemoryDatabase() - genesis = new(Genesis).MustCommit(db) + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: chainConfig, + BaseFee: baseFee} + genesis = gspec.MustCommit(db) ) // Initialize a fresh chain with only a genesis block - blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil) + blockchain, _ := NewBlockChain(db, nil, chainConfig, engine, vm.Config{}, nil) // Create and inject the requested chain if n == 0 { return db, blockchain, nil } if full { // Full block-chain requested - blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed) + blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed, chainConfig) _, err := blockchain.InsertChain(blocks) return db, blockchain, err } // Header-only chain requested - headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed) + headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed, chainConfig) _, err := blockchain.InsertHeaderChain(headers, 1) return db, blockchain, err } // Test fork of length N starting from block i -func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { +func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int), chainConfig *params.ChainConfig, baseFee *big.Int) { // Copy old chain up to #i into a new db - db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full) + db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, chainConfig, baseFee) if err != nil { t.Fatal("could not make new canonical in testFork", err) } @@ -98,12 +101,12 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara headerChainB []*types.Header ) if full { - blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed) + blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed, chainConfig) if _, err := blockchain2.InsertChain(blockChainB); err != nil { t.Fatalf("failed to insert forking chain: %v", err) } } else { - headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed) + headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed, chainConfig) if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil { t.Fatalf("failed to insert forking chain: %v", err) } @@ -184,13 +187,22 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error } func TestLastBlock(t *testing.T) { - _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true) + testLastBlock(t, params.AllEthashProtocolChanges, nil) +} +func TestLastBlockEIP1559(t *testing.T) { + testLastBlock(t, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestLastBlockEIP1559Finalized(t *testing.T) { + testLastBlock(t, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} + +func testLastBlock(t *testing.T, chainConfig *params.ChainConfig, baseFee *big.Int) { + _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } defer blockchain.Stop() - - blocks := makeBlockChain(blockchain.CurrentBlock(), 1, ethash.NewFullFaker(), blockchain.db, 0) + blocks := makeBlockChain(blockchain.CurrentBlock(), 1, ethash.NewFullFaker(), blockchain.db, 0, chainConfig) if _, err := blockchain.InsertChain(blocks); err != nil { t.Fatalf("Failed to insert block: %v", err) } @@ -201,14 +213,30 @@ func TestLastBlock(t *testing.T) { // Tests that given a starting canonical chain of a given size, it can be extended // with various length chains. -func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) } -func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) } +func TestExtendCanonicalHeaders(t *testing.T) { + testExtendCanonical(t, false, params.AllEthashProtocolChanges, nil) +} +func TestExtendCanonicalBlocks(t *testing.T) { + testExtendCanonical(t, true, params.AllEthashProtocolChanges, nil) +} +func TestExtendCanonicalHeadersEIP1559(t *testing.T) { + testExtendCanonical(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestExtendCanonicalBlocksEIP1559(t *testing.T) { + testExtendCanonical(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestExtendCanonicalHeadersEIP1559Finalized(t *testing.T) { + testExtendCanonical(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestExtendCanonicalBlocksEIP1559Finalized(t *testing.T) { + testExtendCanonical(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testExtendCanonical(t *testing.T, full bool) { +func testExtendCanonical(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { length := 5 // Make first chain starting from genesis - _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, processor, err := newCanonical(ethash.NewFaker(), length, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -221,22 +249,38 @@ func testExtendCanonical(t *testing.T, full bool) { } } // Start fork from current height - testFork(t, processor, length, 1, full, better) - testFork(t, processor, length, 2, full, better) - testFork(t, processor, length, 5, full, better) - testFork(t, processor, length, 10, full, better) + testFork(t, processor, length, 1, full, better, chainConfig, baseFee) + testFork(t, processor, length, 2, full, better, chainConfig, baseFee) + testFork(t, processor, length, 5, full, better, chainConfig, baseFee) + testFork(t, processor, length, 10, full, better, chainConfig, baseFee) } // Tests that given a starting canonical chain of a given size, creating shorter // forks do not take canonical ownership. -func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) } -func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) } +func TestShorterForkHeaders(t *testing.T) { + testShorterFork(t, false, params.AllEthashProtocolChanges, nil) +} +func TestShorterForkBlocks(t *testing.T) { + testShorterFork(t, true, params.AllEthashProtocolChanges, nil) +} +func TestShorterForkHeadersEIP1559(t *testing.T) { + testShorterFork(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestShorterForkBlocksEIP1559(t *testing.T) { + testShorterFork(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestShorterForkHeadersEIP1559Finalized(t *testing.T) { + testShorterFork(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestShorterForkBlocksEIP1559Finalized(t *testing.T) { + testShorterFork(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testShorterFork(t *testing.T, full bool) { +func testShorterFork(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { length := 10 // Make first chain starting from genesis - _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, processor, err := newCanonical(ethash.NewFaker(), length, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -249,24 +293,38 @@ func testShorterFork(t *testing.T, full bool) { } } // Sum of numbers must be less than `length` for this to be a shorter fork - testFork(t, processor, 0, 3, full, worse) - testFork(t, processor, 0, 7, full, worse) - testFork(t, processor, 1, 1, full, worse) - testFork(t, processor, 1, 7, full, worse) - testFork(t, processor, 5, 3, full, worse) - testFork(t, processor, 5, 4, full, worse) + testFork(t, processor, 0, 3, full, worse, chainConfig, baseFee) + testFork(t, processor, 0, 7, full, worse, chainConfig, baseFee) + testFork(t, processor, 1, 1, full, worse, chainConfig, baseFee) + testFork(t, processor, 1, 7, full, worse, chainConfig, baseFee) + testFork(t, processor, 5, 3, full, worse, chainConfig, baseFee) + testFork(t, processor, 5, 4, full, worse, chainConfig, baseFee) } // Tests that given a starting canonical chain of a given size, creating longer // forks do take canonical ownership. -func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) } -func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) } +func TestLongerForkHeaders(t *testing.T) { + testLongerFork(t, false, params.AllEthashProtocolChanges, nil) +} +func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true, params.AllEthashProtocolChanges, nil) } +func TestLongerForkHeadersEIP1559(t *testing.T) { + testLongerFork(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestLongerForkBlocksEIP1559(t *testing.T) { + testLongerFork(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestLongerForkHeadersEIP1559Finalized(t *testing.T) { + testLongerFork(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestLongerForkBlocksEIP1559Finalized(t *testing.T) { + testLongerFork(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testLongerFork(t *testing.T, full bool) { +func testLongerFork(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { length := 10 // Make first chain starting from genesis - _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, processor, err := newCanonical(ethash.NewFaker(), length, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -279,24 +337,36 @@ func testLongerFork(t *testing.T, full bool) { } } // Sum of numbers must be greater than `length` for this to be a longer fork - testFork(t, processor, 0, 11, full, better) - testFork(t, processor, 0, 15, full, better) - testFork(t, processor, 1, 10, full, better) - testFork(t, processor, 1, 12, full, better) - testFork(t, processor, 5, 6, full, better) - testFork(t, processor, 5, 8, full, better) + testFork(t, processor, 0, 11, full, better, chainConfig, baseFee) + testFork(t, processor, 0, 15, full, better, chainConfig, baseFee) + testFork(t, processor, 1, 10, full, better, chainConfig, baseFee) + testFork(t, processor, 1, 12, full, better, chainConfig, baseFee) + testFork(t, processor, 5, 6, full, better, chainConfig, baseFee) + testFork(t, processor, 5, 8, full, better, chainConfig, baseFee) } // Tests that given a starting canonical chain of a given size, creating equal // forks do take canonical ownership. -func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) } -func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) } +func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false, params.AllEthashProtocolChanges, nil) } +func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true, params.AllEthashProtocolChanges, nil) } +func TestEqualForkHeadersEIP1559(t *testing.T) { + testEqualFork(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestEqualForkBlocksEIP1559(t *testing.T) { + testEqualFork(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestEqualForkHeadersEIP1559Finalized(t *testing.T) { + testEqualFork(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestEqualForkBlocksEIP1559Finalized(t *testing.T) { + testEqualFork(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testEqualFork(t *testing.T, full bool) { +func testEqualFork(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { length := 10 // Make first chain starting from genesis - _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, processor, err := newCanonical(ethash.NewFaker(), length, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -309,21 +379,37 @@ func testEqualFork(t *testing.T, full bool) { } } // Sum of numbers must be equal to `length` for this to be an equal fork - testFork(t, processor, 0, 10, full, equal) - testFork(t, processor, 1, 9, full, equal) - testFork(t, processor, 2, 8, full, equal) - testFork(t, processor, 5, 5, full, equal) - testFork(t, processor, 6, 4, full, equal) - testFork(t, processor, 9, 1, full, equal) + testFork(t, processor, 0, 10, full, equal, chainConfig, baseFee) + testFork(t, processor, 1, 9, full, equal, chainConfig, baseFee) + testFork(t, processor, 2, 8, full, equal, chainConfig, baseFee) + testFork(t, processor, 5, 5, full, equal, chainConfig, baseFee) + testFork(t, processor, 6, 4, full, equal, chainConfig, baseFee) + testFork(t, processor, 9, 1, full, equal, chainConfig, baseFee) } // Tests that chains missing links do not get accepted by the processor. -func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) } -func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) } +func TestBrokenHeaderChain(t *testing.T) { + testBrokenChain(t, false, params.AllEthashProtocolChanges, nil) +} +func TestBrokenBlockChain(t *testing.T) { + testBrokenChain(t, true, params.AllEthashProtocolChanges, nil) +} +func TestBrokenHeaderChainEIP1559(t *testing.T) { + testBrokenChain(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBrokenBlockChainEIP1559(t *testing.T) { + testBrokenChain(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBrokenHeaderChainEIP1559Finalized(t *testing.T) { + testBrokenChain(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBrokenBlockChainEIP1559Finalized(t *testing.T) { + testBrokenChain(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testBrokenChain(t *testing.T, full bool) { +func testBrokenChain(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { // Make chain starting from genesis - db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full) + db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -331,12 +417,12 @@ func testBrokenChain(t *testing.T, full bool) { // Create a forked chain, and try to insert with a missing link if full { - chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:] + chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed, chainConfig)[1:] if err := testBlockChainImport(chain, blockchain); err == nil { t.Errorf("broken block chain not reported") } } else { - chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed)[1:] + chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed, chainConfig)[1:] if err := testHeaderChainImport(chain, blockchain); err == nil { t.Errorf("broken header chain not reported") } @@ -345,19 +431,45 @@ func testBrokenChain(t *testing.T, full bool) { // Tests that reorganising a long difficult chain after a short easy one // overwrites the canonical numbers and links in the database. -func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) } -func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) } +func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false, params.AllEthashProtocolChanges, nil) } +func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true, params.AllEthashProtocolChanges, nil) } +func TestReorgLongHeadersEIP1559(t *testing.T) { + testReorgLong(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgLongBlocksEIP1559(t *testing.T) { + testReorgLong(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgLongHeadersEIP1559Finalized(t *testing.T) { + testReorgLong(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgLongBlocksEIP1559Finalized(t *testing.T) { + testReorgLong(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testReorgLong(t *testing.T, full bool) { - testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280, full) +func testReorgLong(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { + testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280, full, chainConfig, baseFee) } // Tests that reorganising a short difficult chain after a long easy one // overwrites the canonical numbers and links in the database. -func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) } -func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) } +func TestReorgShortHeaders(t *testing.T) { + testReorgShort(t, false, params.AllEthashProtocolChanges, nil) +} +func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true, params.AllEthashProtocolChanges, nil) } +func TestReorgShortHeadersEIP1559(t *testing.T) { + testReorgShort(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgShortBlocksEIP1559(t *testing.T) { + testReorgShort(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgShortHeadersEIP1559Finalized(t *testing.T) { + testReorgShort(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgShortBlocksEIP1559Finalized(t *testing.T) { + testReorgShort(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testReorgShort(t *testing.T, full bool) { +func testReorgShort(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { // Create a long easy chain vs. a short heavy one. Due to difficulty adjustment // we need a fairly long chain of blocks with different difficulties for a short // one to become heavyer than a long one. The 96 is an empirical value. @@ -369,22 +481,22 @@ func testReorgShort(t *testing.T, full bool) { for i := 0; i < len(diff); i++ { diff[i] = -9 } - testReorg(t, easy, diff, 12615120, full) + testReorg(t, easy, diff, 12615120, full, chainConfig, baseFee) } -func testReorg(t *testing.T, first, second []int64, td int64, full bool) { +func testReorg(t *testing.T, first, second []int64, td int64, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { // Create a pristine chain and database - db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } defer blockchain.Stop() // Insert an easy and a difficult chain afterwards - easyBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(first), func(i int, b *BlockGen) { + easyBlocks, _ := GenerateChain(chainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(first), func(i int, b *BlockGen) { b.OffsetTime(first[i]) }) - diffBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(second), func(i int, b *BlockGen) { + diffBlocks, _ := GenerateChain(chainConfig, blockchain.CurrentBlock(), ethash.NewFaker(), db, len(second), func(i int, b *BlockGen) { b.OffsetTime(second[i]) }) if full { @@ -440,12 +552,24 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool) { } // Tests that the insertion functions detect banned hashes. -func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) } -func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } +func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false, params.AllEthashProtocolChanges, nil) } +func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true, params.AllEthashProtocolChanges, nil) } +func TestBadHeaderHashesEIP1559(t *testing.T) { + testBadHashes(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBadBlockHashesEIP1559(t *testing.T) { + testBadHashes(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBadHeaderHashesEIP1559Finalized(t *testing.T) { + testBadHashes(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBadBlockHashesEIP1559Finalized(t *testing.T) { + testBadHashes(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testBadHashes(t *testing.T, full bool) { +func testBadHashes(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { // Create a pristine chain and database - db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -453,14 +577,14 @@ func testBadHashes(t *testing.T, full bool) { // Create a chain, ban a hash and try to import if full { - blocks := makeBlockChain(blockchain.CurrentBlock(), 3, ethash.NewFaker(), db, 10) + blocks := makeBlockChain(blockchain.CurrentBlock(), 3, ethash.NewFaker(), db, 10, chainConfig) BadHashes[blocks[2].Header().Hash()] = true defer func() { delete(BadHashes, blocks[2].Header().Hash()) }() _, err = blockchain.InsertChain(blocks) } else { - headers := makeHeaderChain(blockchain.CurrentHeader(), 3, ethash.NewFaker(), db, 10) + headers := makeHeaderChain(blockchain.CurrentHeader(), 3, ethash.NewFaker(), db, 10, chainConfig) BadHashes[headers[2].Hash()] = true defer func() { delete(BadHashes, headers[2].Hash()) }() @@ -474,18 +598,34 @@ func testBadHashes(t *testing.T, full bool) { // Tests that bad hashes are detected on boot, and the chain rolled back to a // good state prior to the bad hash. -func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) } -func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } +func TestReorgBadHeaderHashes(t *testing.T) { + testReorgBadHashes(t, false, params.AllEthashProtocolChanges, nil) +} +func TestReorgBadBlockHashes(t *testing.T) { + testReorgBadHashes(t, true, params.AllEthashProtocolChanges, nil) +} +func TestReorgBadHeaderHashesEIP1559(t *testing.T) { + testReorgBadHashes(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgBadBlockHashesEIP1559(t *testing.T) { + testReorgBadHashes(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgBadHeaderHashesEIP1559Finalized(t *testing.T) { + testReorgBadHashes(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestReorgBadBlockHashesEIP1559Finalized(t *testing.T) { + testReorgBadHashes(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testReorgBadHashes(t *testing.T, full bool) { +func testReorgBadHashes(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { // Create a pristine chain and database - db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } // Create a chain, import and ban afterwards - headers := makeHeaderChain(blockchain.CurrentHeader(), 4, ethash.NewFaker(), db, 10) - blocks := makeBlockChain(blockchain.CurrentBlock(), 4, ethash.NewFaker(), db, 10) + headers := makeHeaderChain(blockchain.CurrentHeader(), 4, ethash.NewFaker(), db, 10, chainConfig) + blocks := makeBlockChain(blockchain.CurrentBlock(), 4, ethash.NewFaker(), db, 10, chainConfig) if full { if _, err = blockchain.InsertChain(blocks); err != nil { @@ -529,13 +669,29 @@ func testReorgBadHashes(t *testing.T, full bool) { } // Tests chain insertions in the face of one entity containing an invalid nonce. -func TestHeadersInsertNonceError(t *testing.T) { testInsertNonceError(t, false) } -func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) } +func TestHeadersInsertNonceError(t *testing.T) { + testInsertNonceError(t, false, params.AllEthashProtocolChanges, nil) +} +func TestBlocksInsertNonceError(t *testing.T) { + testInsertNonceError(t, true, params.AllEthashProtocolChanges, nil) +} +func TestHeadersInsertNonceErrorEIP1559(t *testing.T) { + testInsertNonceError(t, false, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBlocksInsertNonceErrorEIP1559(t *testing.T) { + testInsertNonceError(t, true, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestHeadersInsertNonceErrorEIP1559Finalized(t *testing.T) { + testInsertNonceError(t, false, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestBlocksInsertNonceErrorEIP1559Finalized(t *testing.T) { + testInsertNonceError(t, true, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} -func testInsertNonceError(t *testing.T, full bool) { +func testInsertNonceError(t *testing.T, full bool, chainConfig *params.ChainConfig, baseFee *big.Int) { for i := 1; i < 25 && !t.Failed(); i++ { // Create a pristine chain and database - db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -548,7 +704,7 @@ func testInsertNonceError(t *testing.T, full bool) { failNum uint64 ) if full { - blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0) + blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0, chainConfig) failAt = rand.Int() % len(blocks) failNum = blocks[failAt].NumberU64() @@ -556,7 +712,7 @@ func testInsertNonceError(t *testing.T, full bool) { blockchain.engine = ethash.NewFakeFailer(failNum) failRes, err = blockchain.InsertChain(blocks) } else { - headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0) + headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0, chainConfig) failAt = rand.Int() % len(headers) failNum = headers[failAt].Number.Uint64() @@ -1265,9 +1421,19 @@ done: } -// Tests if the canonical block can be fetched from the database during chain insertion. func TestCanonicalBlockRetrieval(t *testing.T) { - _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true) + testCanonicalBlockRetrieval(t, params.AllEthashProtocolChanges, nil) +} +func TestCanonicalBlockRetrievalEIP1559(t *testing.T) { + testCanonicalBlockRetrieval(t, params.EIP1559ChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} +func TestCanonicalBlockRetrievalEIP1559Finalized(t *testing.T) { + testCanonicalBlockRetrieval(t, params.EIP1559FinalizedChainConfig, new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) +} + +// Tests if the canonical block can be fetched from the database during chain insertion. +func testCanonicalBlockRetrieval(t *testing.T, chainConfig *params.ChainConfig, baseFee *big.Int) { + _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true, chainConfig, baseFee) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -2363,30 +2529,3 @@ func TestDeleteCreateRevert(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } } - -/* - We need to test: - - 1. blockChain.insertChain works as usual before activation - 2. blockChain.insertChain works as expected during transition phase (with both types of transactions) - 3. blockChain.insertChain works as expected after finalization (with only EIP1559 transactions) - -*/ - -// TestEIP1559 tests the changes introduced by the EIP1559 forks -func TestEIP1559BlockChain(t *testing.T) { - //testEIP1559BlockChain(t) - //testEIP1559BlockChainAfterFinalization(t) -} - -// TestEIP1559Initialization tests the first EIP1559 fork which introduces a second gas pool and transaction type -// that coexists with the legacy pool and transaction type -func testEIP1559BlockChain(t *testing.T) { - panic("implement me") -} - -// TestEIP1559Finalization tests the second EIP1559 fork which finalizes the new gas pool and transaction type -// and deprecates the legacy pool and transaction type -func testEIP1559BlockChainAfterFinalization(t *testing.T) { - panic("implement me") -} diff --git a/core/chain_makers.go b/core/chain_makers.go index 20f8509cdd5e..fac499300a95 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -278,8 +278,8 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } // makeHeaderChain creates a deterministic chain of headers rooted at parent. -func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header { - blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed) +func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int, chainConfig *params.ChainConfig) []*types.Header { + blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed, chainConfig) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { headers[i] = block.Header() @@ -288,8 +288,8 @@ func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db et } // makeBlockChain creates a deterministic chain of blocks rooted at parent. -func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block { - blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) { +func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int, chainConfig *params.ChainConfig) []*types.Block { + blocks, _ := GenerateChain(chainConfig, parent, engine, db, n, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) }) return blocks