diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index e30572e0a..6215a5d1f 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -299,9 +299,28 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // state is modified during execution, make sure to copy it if necessary. 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. - if call.GasPrice == nil { + // 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) { + 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) { + 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 } @@ -319,8 +338,12 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // about the transaction and calling mechanisms. vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) gaspool := new(core.GasPool).AddGas(math.MaxUint64) + var gp1559 *core.GasPool + if b.config.IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(math.MaxUint64) + } - return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + return core.NewStateTransition(vmenv, msg, gaspool, gp1559).TransitionDb() } // SendTransaction updates the pending block to include the given transaction. @@ -329,6 +352,20 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.mu.Lock() defer b.mu.Unlock() + // EIP1559 guards + if b.config.IsEIP1559Finalized(b.blockchain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return core.ErrTxNotEIP1559 + } + if !b.config.IsEIP1559(b.blockchain.CurrentBlock().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 + } + sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx) if err != nil { panic(fmt.Errorf("invalid transaction: %v", err)) @@ -455,6 +492,8 @@ func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } func (m callmsg) Value() *big.Int { return m.CallMsg.Value } func (m callmsg) Data() []byte { return m.CallMsg.Data } +func (m callmsg) GasPremium() *big.Int { return m.CallMsg.GasPremium } +func (m callmsg) FeeCap() *big.Int { return m.CallMsg.FeeCap } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 7e0a68c08..0da73103d 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -54,7 +54,7 @@ func TestSimulatedBackend(t *testing.T) { // generate a transaction and confirm you can retrieve it code := `6060604052600a8060106000396000f360606040526008565b00` var gas uint64 = 3000000 - tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code)) + tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code), nil, nil) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) err = sim.SendTransaction(context.Background(), tx) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 499b4bda0..ac7cd120e 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -227,9 +227,9 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i // Create the transaction, sign it and schedule it for execution var rawTx *types.Transaction if contract == nil { - rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input) + rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input, nil, nil) } else { - rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input) + rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input, nil, nil) } if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index e0141f46e..93779ca99 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -62,7 +62,7 @@ func TestWaitDeployed(t *testing.T) { defer backend.Close() // Create the transaction. - tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code)) + tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code), nil, nil) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) // Wait for it to get mined in the background. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index d34f8c28d..4522f7696 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -775,6 +775,7 @@ func testExternalUI(api *core.SignerAPI) { []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), common.HexToHash("0x0000H45H"), types.BlockNonce{}, + nil, } cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) if err != nil { diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 905eeb24a..e37294215 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -481,7 +481,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) { amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil)) amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil)) - tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil) + tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil, nil, nil) signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index b6aa3706b..70e1f5b8a 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -491,7 +491,16 @@ func (api *RetestethAPI) mineBlock() error { if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { misc.ApplyDAOHardFork(statedb) } - gasPool := new(core.GasPool).AddGas(header.GasLimit) + + var gp1559 *core.GasPool + var gasPool *core.GasPool + if api.chainConfig.IsEIP1559(header.Number) { + gasPool = new(core.GasPool).AddGas(params.MaxGasEIP1559 - header.GasLimit) + gp1559 = new(core.GasPool).AddGas(header.GasLimit) + } else { + gasPool = new(core.GasPool).AddGas(header.GasLimit) + } + txCount := 0 var txs []*types.Transaction var receipts []*types.Receipt @@ -513,6 +522,7 @@ func (api *RetestethAPI) mineBlock() error { api.blockchain, &api.author, gasPool, + gp1559, statedb, header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(), ) @@ -657,7 +667,11 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + var gp1559 *core.GasPool + if vmenv.ChainConfig().IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(tx.Gas()) + } + if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), gp1559); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state @@ -770,7 +784,11 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + var gp1559 *core.GasPool + if vmenv.ChainConfig().IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(tx.Gas()) + } + if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), gp1559); err != nil { return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 710f44805..5da135444 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -65,7 +65,7 @@ func TestReimportMirroredState(t *testing.T) { // We want to simulate an empty middle block, having the same state as the // first one. The last is needs a state change again to force a reorg. if i != 1 { - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, nil, nil), signer, key) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, nil, nil, nil, nil), signer, key) if err != nil { panic(err) } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 3cff2d9fe..dd3e9eac6 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -71,6 +71,7 @@ var ( errInvalidDifficulty = errors.New("non-positive difficulty") errInvalidMixDigest = errors.New("invalid mix digest") errInvalidPoW = errors.New("invalid proof-of-work") + errGasLimitSet = errors.New("GasLimit should not be set after EIP1559 has finalized") ) // Author implements consensus.Engine, returning the header's coinbase as the @@ -258,26 +259,34 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * if expected.Cmp(header.Difficulty) != 0 { return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) } - // Verify that the gas limit is <= 2^63-1 - cap := uint64(0x7fffffffffffffff) - if header.GasLimit > cap { - return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) - } - // Verify that the gasUsed is <= gasLimit - if header.GasUsed > header.GasLimit { - return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) - } - // Verify that the gas limit remains within allowed bounds - diff := int64(parent.GasLimit) - int64(header.GasLimit) - if diff < 0 { - diff *= -1 - } - limit := parent.GasLimit / params.GasLimitBoundDivisor + // If EIP1559 is not active we need to verify that the GasLimit field is valid according to the legacy rules + if !chain.Config().IsEIP1559(header.Number) { + // Verify that the gas limit is <= 2^63-1 + cap := uint64(0x7fffffffffffffff) + if header.GasLimit > cap { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + + // Verify that the gas limit remains within allowed bounds + diff := int64(parent.GasLimit) - int64(header.GasLimit) + if diff < 0 { + diff *= -1 + } + limit := parent.GasLimit / params.GasLimitBoundDivisor - if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { - return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) + if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { + return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) + } + // If EIP1559 is active, assert that the GasLimit field is valid according to the EIP1559 rules + } else if err := misc.VerifyEIP1559GasLimit(chain.Config(), header); err != nil { + return err } + // Verify that the block number is parent's +1 if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { return consensus.ErrInvalidNumber @@ -289,6 +298,9 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent * } } // If all checks passed, validate any special fields for hard forks + if err := misc.VerifyEIP1559BaseFee(chain.Config(), header, parent); err != nil { + return err + } if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil { return err } @@ -583,21 +595,41 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainReader, header *t func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra, - }) + if header.BaseFee == nil { + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra, + }) + } else { + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra, + header.BaseFee, + }) + } + hasher.Sum(hash[:0]) return hash } diff --git a/consensus/misc/forks.go b/consensus/misc/forks.go index 4a5e7c37e..ca2e59ffb 100644 --- a/consensus/misc/forks.go +++ b/consensus/misc/forks.go @@ -17,13 +17,24 @@ package misc import ( + "errors" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) +var ( + errInvalidInitialBaseFee = fmt.Errorf("initial BaseFee must equal %d", params.EIP1559InitialBaseFee) + errInvalidBaseFee = errors.New("invalid BaseFee") + errMissingParentBaseFee = errors.New("parent header is missing BaseFee") + errMissingBaseFee = errors.New("current header is missing BaseFee") + errHaveBaseFee = fmt.Errorf("BaseFee should not be set before block %d", params.EIP1559ForkBlockNumber) + errInvalidEIP1559FinalizedGasLimit = fmt.Errorf("after EIP1559 finalization, GasLimit must equal %d", params.MaxGasEIP1559) +) + // VerifyForkHashes verifies that blocks conforming to network hard-forks do have // the correct hashes, to avoid clients going off on different chains. This is an // optional feature. @@ -41,3 +52,60 @@ func VerifyForkHashes(config *params.ChainConfig, header *types.Header, uncle bo // All ok, return return nil } + +// VerifyEIP1559BaseFee verifies that the EIP1559 BaseFee field is valid for the current block height +func VerifyEIP1559BaseFee(config *params.ChainConfig, header, parent *types.Header) error { + // If we are at the EIP1559 fork block the BaseFee needs to be equal to params.EIP1559InitialBaseFee + if config.EIP1559Block != nil && config.EIP1559Block.Cmp(header.Number) == 0 { + if header.BaseFee == nil || header.BaseFee.Cmp(new(big.Int).SetUint64(params.EIP1559InitialBaseFee)) != 0 { + return errInvalidInitialBaseFee + } + return nil + } + // Verify the BaseFee is valid if we are past the EIP1559 activation block + if config.IsEIP1559(header.Number) { + // A valid BASEFEE is one such that abs(BASEFEE - PARENT_BASEFEE) <= max(1, PARENT_BASEFEE // BASEFEE_MAX_CHANGE_DENOMINATOR) + if parent.BaseFee == nil { + return errMissingParentBaseFee + } + if header.BaseFee == nil { + return errMissingBaseFee + } + diff := new(big.Int).Sub(header.BaseFee, parent.BaseFee) + if diff.Sign() < 0 { + 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 diff.Cmp(max) > 0 { + return errInvalidBaseFee + } + return nil + } + // If we are before the EIP1559 activation block the current and parent BaseFees should be nil + if header.BaseFee != nil || parent.BaseFee != nil { + return errHaveBaseFee + } + return nil +} + +// VerifyEIP1559GasLimit verifies that the header.GasLimit field is valid for the current block height +// Only call this after activation has been confirmed (config.IsEIP1559(header.Number) == true) +func VerifyEIP1559GasLimit(config *params.ChainConfig, header *types.Header) error { + // If EIP1559 has been finalized then header.GasLimit should be equal to the MaxGasEIP1559 (entire limit is in EIP1559 pool) + if config.IsEIP1559Finalized(header.Number) { + if header.GasLimit != params.MaxGasEIP1559 { + return errInvalidEIP1559FinalizedGasLimit + } + return nil + } + // Else if we are between activation and finalization, header.GasLimit must be valid based on the decay function + numOfIncrements := new(big.Int).Sub(header.Number, config.EIP1559Block).Uint64() + expectedGasLimit := (params.MaxGasEIP1559 / 2) + (numOfIncrements * params.EIP1559GasIncrementAmount) + if header.GasLimit != expectedGasLimit { + return fmt.Errorf("invalid GasLimit: have %d, need %d", header.GasLimit, expectedGasLimit) + } + return nil +} diff --git a/core/bench_test.go b/core/bench_test.go index d7a5e11c2..24b35bfda 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), 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,6 +124,8 @@ func genTxRing(naccounts int) func(int, *BlockGen) { ringAddrs[to], benchRootFunds, params.TxGas, + new(big.Int), + nil, nil, nil, ) diff --git a/core/block_validator.go b/core/block_validator.go index b36ca56d7..af8a77c92 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -18,7 +18,9 @@ package core import ( "fmt" + "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" @@ -137,3 +139,63 @@ func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { } return limit } + +func CalcGasLimitAndBaseFee(config *params.ChainConfig, parent *types.Block, gasFloor, gasCeil uint64) (uint64, *big.Int) { + if !config.IsEIP1559(new(big.Int).Add(parent.Number(), common.Big1)) { + return CalcGasLimit(parent, gasFloor, gasCeil), nil + } + return calcGasLimitAndBaseFee(config, parent) +} + +// 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) { + 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 + // and the GasLimit is split evenly between the two pools + if config.EIP1559Block.Cmp(height) == 0 { + 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, + + 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) + div := new(big.Int).Div(mul, new(big.Int).SetUint64(params.TargetGasUsed)) + div2 := new(big.Int).Div(div, new(big.Int).SetUint64(params.BaseFeeMaxChangeDenominator)) + baseFee := new(big.Int).Add(parent.BaseFee(), div2) + + // 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 { + 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 + if config.IsEIP1559Finalized(new(big.Int).Add(parent.Number(), common.Big1)) { + return params.MaxGasEIP1559, baseFee + } + + // Otherwise calculate how much of the MaxGasEIP1559 serves as the limit for the EIP1559 pool + // The GasLimit for the legacy pool is (params.MaxGasEIP1559 - eip1559GasLimit) + numOfIncrements := new(big.Int).Sub(height, config.EIP1559Block).Uint64() + eip1559GasLimit := (params.MaxGasEIP1559 / 2) + (numOfIncrements * params.EIP1559GasIncrementAmount) + return eip1559GasLimit, baseFee +} diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 06e2ba1a4..dd5ba9e16 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) } @@ -197,3 +517,166 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { t.Errorf("verification count too large: have %d, want below %d", verified, 2*threads) } } + +// 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/blockchain_test.go b/core/blockchain_test.go index 34f68a644..f70321fc1 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() @@ -606,7 +762,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), 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 +995,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), signer, key1) - swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, 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 +1012,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), 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), 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 +1037,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), 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), 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), 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 } }) @@ -948,7 +1104,7 @@ func TestLogReorgs(t *testing.T) { blockchain.SubscribeRemovedLogsEvent(rmLogsCh) chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { if i == 1 { - tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code, nil, nil), signer, key1) if err != nil { t.Fatalf("failed to create tx: %v", err) } @@ -1029,7 +1185,7 @@ func TestLogRebirth(t *testing.T) { chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { if i == 1 { - tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code, nil, nil), signer, key1) if err != nil { t.Fatalf("failed to create tx: %v", err) } @@ -1049,7 +1205,7 @@ func TestLogRebirth(t *testing.T) { // Generate long reorg chain forkChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { if i == 1 { - tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code, nil, nil), signer, key1) if err != nil { t.Fatalf("failed to create tx: %v", err) } @@ -1159,7 +1315,7 @@ func TestSideLogRebirth(t *testing.T) { // Generate side chain with lower difficulty sideChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) { if i == 1 { - tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1) + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code, nil, nil), signer, key1) if err != nil { t.Fatalf("failed to create tx: %v", err) } @@ -1204,7 +1360,7 @@ func TestReorgSideEvent(t *testing.T) { } replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) { - tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), nil), signer, key1) + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), nil, nil, nil), signer, key1) if i == 2 { gen.OffsetTime(-9) } @@ -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) } @@ -1332,7 +1498,7 @@ func TestEIP155Transition(t *testing.T) { tx *types.Transaction err error basicTx = func(signer types.Signer) (*types.Transaction, error) { - return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil, nil, nil), signer, key) } ) switch i { @@ -1395,7 +1561,7 @@ func TestEIP155Transition(t *testing.T) { tx *types.Transaction err error basicTx = func(signer types.Signer) (*types.Transaction, error) { - return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil, nil, nil), signer, key) } ) if i == 0 { @@ -1443,11 +1609,11 @@ func TestEIP161AccountRemoval(t *testing.T) { ) switch i { case 0: - tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil, nil, nil), signer, key) case 1: - tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil, nil, nil), signer, key) case 2: - tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil, nil, nil), signer, key) } if err != nil { t.Fatal(err) @@ -2163,7 +2329,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in for txi := 0; txi < numTxs; txi++ { uniq := uint64(i*numTxs + txi) recipient := recipientFn(uniq) - tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil, nil, nil), signer, testBankKey) if err != nil { b.Error(err) } @@ -2344,11 +2510,11 @@ func TestDeleteCreateRevert(t *testing.T) { b.SetCoinbase(common.Address{1}) // One transaction to AAAA tx, _ := types.SignTx(types.NewTransaction(0, aa, - big.NewInt(0), 50000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + big.NewInt(0), 50000, big.NewInt(1), nil, nil, nil), types.HomesteadSigner{}, key) b.AddTx(tx) // One transaction to BBBB tx, _ = types.SignTx(types.NewTransaction(1, bb, - big.NewInt(0), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + big.NewInt(0), 100000, big.NewInt(1), nil, nil, nil), types.HomesteadSigner{}, key) b.AddTx(tx) }) // Import the canonical chain diff --git a/core/chain_makers.go b/core/chain_makers.go index 0b0fcdb4a..fac499300 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -39,10 +39,11 @@ type BlockGen struct { header *types.Header statedb *state.StateDB - gasPool *GasPool - txs []*types.Transaction - receipts []*types.Receipt - uncles []*types.Header + gasPool *GasPool + gasPool1559 *GasPool + txs []*types.Transaction + receipts []*types.Receipt + uncles []*types.Header config *params.ChainConfig engine consensus.Engine @@ -58,7 +59,16 @@ func (b *BlockGen) SetCoinbase(addr common.Address) { panic("coinbase can only be set once") } b.header.Coinbase = addr - b.gasPool = new(GasPool).AddGas(b.header.GasLimit) + // If EIP1559 is initialized then header.GasLimit is for the EIP1559 pool + // and the difference between the MaxGasEIP1559 and header.GasLimit is the limit for the legacy pool + // Once EIP1559 is finalized the header.GasLimit is the entire MaxGasEIP1559 + // so no gas will be allocated to the legacy pool + if b.config.IsEIP1559(b.header.Number) { + b.gasPool = new(GasPool).AddGas(params.MaxGasEIP1559 - b.header.GasLimit) + b.gasPool1559 = new(GasPool).AddGas(b.header.GasLimit) + } else { // If we are before EIP1559 activation then we use header.GasLimit for the legacy pool + b.gasPool = new(GasPool).AddGas(b.header.GasLimit) + } } // SetExtra sets the extra data field of the generated block. @@ -102,8 +112,9 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { if b.gasPool == nil { b.SetCoinbase(common.Address{}) } + b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) + receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.gasPool1559, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) if err != nil { panic(err) } @@ -248,6 +259,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S time = parent.Time() + 10 // block time is fixed at 10 seconds } + gasLimit, baseFee := CalcGasLimitAndBaseFee(chain.Config(), parent, parent.GasLimit(), parent.GasLimit()) return &types.Header{ Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), ParentHash: parent.Hash(), @@ -258,15 +270,16 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S Difficulty: parent.Difficulty(), UncleHash: parent.UncleHash(), }), - GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + GasLimit: gasLimit, + BaseFee: baseFee, Number: new(big.Int).Add(parent.Number(), common.Big1), Time: time, } } // 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() @@ -275,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 diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 32e3888d5..94f4b3ea7 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), 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), signer, key1) - tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, 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,320 @@ 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()) + } +} + +// TestEIP1559 tests the changes introduced by the EIP1559 forks +func TestEIP1559GenerateChain(t *testing.T) { + generateChainBeforeActivation(t) + generateChainDuringTransition(t) + generateChainAfterFinalization(t) + generateChainAfterFinalization2(t) +} + +// 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() { + 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)}}, + } + 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 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") + 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).SetUint64(params.EIP1559InitialBaseFee), + } + 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 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 { + 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).SetUint64(params.EIP1559InitialBaseFee), + } + 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: new(big.Int).SetUint64((params.EIP1559InitialBaseFee * params.TxGas) + 1000000)}}, + BaseFee: new(big.Int).SetUint64(params.EIP1559InitialBaseFee), + } + 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).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) + } + }) + + // 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() != 2625000989000 { + 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()) + } } diff --git a/core/evm.go b/core/evm.go index b654bbd47..e93155327 100644 --- a/core/evm.go +++ b/core/evm.go @@ -54,7 +54,8 @@ 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/gen_genesis.go b/core/gen_genesis.go index bb8ea1d6a..1dcced1a8 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 } diff --git a/core/genesis.go b/core/genesis.go index df0c96798..40290692f 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 { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 8c8affffd..215c5c1e9 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -271,8 +271,8 @@ func TestBlockReceiptStorage(t *testing.T) { db := NewMemoryDatabase() // Create a live block since we need metadata to reconstruct the receipt - tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) - tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil) + tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil, nil, nil) + tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil, nil, nil) body := &types.Body{Transactions: types.Transactions{tx1, tx2}} diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index c09bff010..4a7ce13d6 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -66,9 +66,9 @@ func TestLookupStorage(t *testing.T) { t.Run(tc.name, func(t *testing.T) { db := NewMemoryDatabase() - tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) - tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) - tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) + tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}, nil, nil) + tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}, nil, nil) + tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}, nil, nil) txs := []*types.Transaction{tx1, tx2, tx3} block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index cb85a05b5..bcb584b8f 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -51,8 +51,19 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) { var ( header = block.Header() - gaspool = new(GasPool).AddGas(block.GasLimit()) + gaspool *GasPool + gp1559 *GasPool ) + // If EIP1559 is initialized then header.GasLimit is for the EIP1559 pool + // and the difference between the MaxGasEIP1559 and header.GasLimit is the limit for the legacy pool + // Once EIP1559 is finalized the header.GasLimit is the entire MaxGasEIP1559 + // so no gas will be allocated to the legacy pool + if p.config.IsEIP1559(block.Number()) { + gaspool = new(GasPool).AddGas(params.MaxGasEIP1559 - block.GasLimit()) + gp1559 = new(GasPool).AddGas(block.GasLimit()) + } else { // If we are before EIP1559 activation then we use header.GasLimit for the legacy pool + gaspool = new(GasPool).AddGas(block.GasLimit()) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { // If block precaching was interrupted, abort @@ -61,7 +72,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c } // Block precaching permitted to continue, execute the transaction statedb.Prepare(tx.Hash(), block.Hash(), i) - if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil { + if err := precacheTransaction(p.config, p.bc, nil, gaspool, gp1559, statedb, header, tx, cfg); err != nil { return // Ugh, something went horribly wrong, bail out } } @@ -70,7 +81,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { +func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool, gp1559 *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { // Convert the transaction into an executable message and pre-cache its sender msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { @@ -80,6 +91,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co context := NewEVMContext(msg, header, bc, author) vm := vm.NewEVM(context, statedb, config, cfg) - _, _, _, err = ApplyMessage(vm, msg, gaspool) + _, _, _, err = ApplyMessage(vm, msg, gaspool, gp1559) return err } diff --git a/core/state_processor.go b/core/state_processor.go index cfe17d587..8a2520202 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -59,8 +59,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg usedGas = new(uint64) header = block.Header() allLogs []*types.Log - gp = new(GasPool).AddGas(block.GasLimit()) + gp *GasPool + gp1559 *GasPool ) + // If EIP1559 is initialized then header.GasLimit is for the EIP1559 pool + // and the difference between the MaxGasEIP1559 and header.GasLimit is the limit for the legacy pool + // Once EIP1559 is finalized the header.GasLimit is the entire MaxGasEIP1559 + // so no gas will be allocated to the legacy pool + if p.config.IsEIP1559(block.Number()) { + gp = new(GasPool).AddGas(params.MaxGasEIP1559 - block.GasLimit()) + gp1559 = new(GasPool).AddGas(block.GasLimit()) + } else { // If we are before EIP1559 activation then we use header.GasLimit for the legacy pool + gp = new(GasPool).AddGas(block.GasLimit()) + } + // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) @@ -68,7 +80,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Iterate over and process the individual transactions for i, tx := range block.Transactions() { statedb.Prepare(tx.Hash(), block.Hash(), i) - receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg) + receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, gp1559, statedb, header, tx, usedGas, cfg) if err != nil { return nil, nil, 0, err } @@ -85,7 +97,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp, gp1559 *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, err @@ -96,7 +108,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // about the transaction and calling mechanisms. vmenv := vm.NewEVM(context, statedb, config, cfg) // Apply the transaction to the current state (included in the env) - _, gas, failed, err := ApplyMessage(vmenv, msg, gp) + _, gas, failed, err := ApplyMessage(vmenv, msg, gp, gp1559) if err != nil { return nil, err } diff --git a/core/state_transition.go b/core/state_transition.go index bef6e9b0e..9f1e021ea 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -28,7 +28,8 @@ import ( ) var ( - errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") + errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") + errInsufficientCoinbaseBalance = errors.New("insufficient coinbase balance to apply a negative coinbase credit") ) /* @@ -49,15 +50,18 @@ The state transitioning model does all the necessary work to work out a valid ne 6) Derive new state root */ type StateTransition struct { - gp *GasPool - msg Message - gas uint64 - gasPrice *big.Int - initialGas uint64 - value *big.Int - data []byte - state vm.StateDB - evm *vm.EVM + gp *GasPool + gp1559 *GasPool + msg Message + gas uint64 + gasPrice *big.Int + initialGas uint64 + value *big.Int + data []byte + state vm.StateDB + evm *vm.EVM + isEIP1559 bool + eip1559GasPrice *big.Int } // Message represents a message sent to a contract. @@ -73,6 +77,9 @@ type Message interface { Nonce() uint64 CheckNonce() bool Data() []byte + + GasPremium() *big.Int + FeeCap() *big.Int } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. @@ -113,16 +120,27 @@ 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 *GasPool) *StateTransition { - return &StateTransition{ - gp: gp, - evm: evm, - msg: msg, - gasPrice: msg.GasPrice(), - value: msg.Value(), - data: msg.Data(), - state: evm.StateDB, +func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTransition { + 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, + evm: evm, + msg: msg, + gasPrice: msg.GasPrice(), + value: msg.Value(), + data: msg.Data(), + state: evm.StateDB, + isEIP1559: isEIP1559, + } + if isEIP1559 { + // EP1559 gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap) + st.eip1559GasPrice = new(big.Int).Add(evm.BaseFee, msg.GasPremium()) + if st.eip1559GasPrice.Cmp(msg.FeeCap()) > 0 { + st.eip1559GasPrice.Set(msg.FeeCap()) + } } + return st } // ApplyMessage computes the new state by applying the given message @@ -132,8 +150,8 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { - return NewStateTransition(evm, msg, gp).TransitionDb() +func ApplyMessage(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) ([]byte, uint64, bool, error) { + return NewStateTransition(evm, msg, gp, gp1559).TransitionDb() } // to returns the recipient of the message. @@ -154,6 +172,29 @@ func (st *StateTransition) useGas(amount uint64) error { } func (st *StateTransition) buyGas() error { + if st.isEIP1559 { + return st.buyGasEIP1559() + } + return st.buyGasLegacy() +} + +func (st *StateTransition) buyGasEIP1559() error { + // tx.origin pays gasPrice * tx.gas + mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.eip1559GasPrice) + if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { + return errInsufficientBalanceForGas + } + if err := st.gp1559.SubGas(st.msg.Gas()); err != nil { + return err + } + st.gas += st.msg.Gas() + + st.initialGas = st.msg.Gas() + st.state.SubBalance(st.msg.From(), mgval) + return nil +} + +func (st *StateTransition) buyGasLegacy() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { return errInsufficientBalanceForGas @@ -178,6 +219,26 @@ func (st *StateTransition) preCheck() error { return ErrNonceTooLow } } + // If we have reached the EIP1559 finalization block and we do not conform with EIP1559, throw an error + if st.evm.ChainConfig().IsEIP1559Finalized(st.evm.BlockNumber) && !st.isEIP1559 { + return ErrTxNotEIP1559 + } + // If we are before the EIP1559 activation block, throw an error if we have EIP1559 fields or do not have a GasPrice + if !st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil || st.gp1559 != nil || st.evm.BaseFee != nil || st.msg.GasPrice() == nil) { + return ErrTxIsEIP1559 + } + // If transaction has both legacy and EIP1559 fields, throw an error + if (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil) && st.msg.GasPrice() != nil { + return ErrTxSetsLegacyAndEIP1559Fields + } + // We need a BaseFee if we are past EIP1559 activation + if st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && st.evm.BaseFee == nil { + return ErrNoBaseFee + } + // We need either a GasPrice or a FeeCap and GasPremium to be set + if st.msg.GasPrice() == nil && (st.msg.GasPremium() == nil || st.msg.FeeCap() == nil) { + return ErrMissingGasFields + } return st.buyGas() } @@ -227,12 +288,36 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo } } st.refundGas() + if st.isEIP1559 { + // block.coinbase gains (gasprice - BASEFEE) * gasused + coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(st.eip1559GasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed())) + // If gasprice < BASEFEE (due to the fee_cap), this means that the block.coinbase loses funds from this operation; + // in this case, check that the post-balance is non-negative and throw an exception if it is negative. + if coinBaseCredit.Sign() < 0 { + coinbaseBal := st.state.GetBalance(st.evm.Coinbase) + postBalance := new(big.Int).Add(coinbaseBal, coinBaseCredit) + if postBalance.Sign() < 0 { + return nil, 0, vmerr != nil, errInsufficientCoinbaseBalance + } + } + st.state.AddBalance(st.evm.Coinbase, coinBaseCredit) + + return ret, st.gasUsed(), vmerr != nil, err + } st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return ret, st.gasUsed(), vmerr != nil, err } func (st *StateTransition) refundGas() { + if st.isEIP1559 { + st.refundGasEIP1559() + return + } + st.refundGasLegacy() +} + +func (st *StateTransition) refundGasLegacy() { // Apply refund counter, capped to half of the used gas. refund := st.gasUsed() / 2 if refund > st.state.GetRefund() { @@ -249,6 +334,24 @@ func (st *StateTransition) refundGas() { st.gp.AddGas(st.gas) } +func (st *StateTransition) refundGasEIP1559() { + // Apply refund counter, capped to half of the used gas. + refund := st.gasUsed() / 2 + if refund > st.state.GetRefund() { + refund = st.state.GetRefund() + } + st.gas += refund + + // tx.origin gets refunded gasprice * (tx.gas - gasused) + txGasSubUsed := new(big.Int).Sub(new(big.Int).SetUint64(st.msg.Gas()), new(big.Int).SetUint64(st.gasUsed())) + remaining := new(big.Int).Mul(st.eip1559GasPrice, txGasSubUsed) + st.state.AddBalance(st.msg.From(), remaining) + + // Also return remaining gas to the block gas counter so it is + // available for the next transaction. + st.gp1559.AddGas(st.gas) +} + // gasUsed returns the amount of gas used up by the state transition. func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gas diff --git a/core/tx_list.go b/core/tx_list.go index 75bfdaeda..6b87b2d66 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 d579f501a..0248b2b8b 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 f7032dbd1..f151d282f 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -76,6 +76,25 @@ var ( // than some meaningful limit a user might use. This is not a consensus error // making the transaction invalid, rather a DOS protection. ErrOversizedData = errors.New("oversized data") + + // ErrTxNotEIP1559 is returned if we have reached the EIP1559 finalized block height + // and the input transaction does not conform to with EIP1559 + ErrTxNotEIP1559 = fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", params.EIP1559ForkFinalizedBlockNumber) + + // ErrTxIsEIP1559 is returned if we have not reached the EIP1559 activation block height + // and the input transaction is not of the legacy type + ErrTxIsEIP1559 = fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", params.EIP1559ForkBlockNumber) + + // ErrTxSetsLegacyAndEIP1559Fields is returned if a transaction attempts to set + // both legacy (GasPrice) and EIP1559 (GasPremium and FeeCap) fields + ErrTxSetsLegacyAndEIP1559Fields = errors.New("transaction sets both legacy and EIP1559 fields") + + // ErrNoBaseFee is returned if we are past the EIP1559 activation block but + // the current header does not provide a BaseFee + ErrNoBaseFee = errors.New("current header does not provide the BaseFee needed to process EIP1559 transactions") + + // ErrMissingGasFields is returned if neither GasPrice nor GasPremium and FeeCap are set + ErrMissingGasFields = errors.New("either GasPrice or GasPremium and FeeCap need to be set") ) var ( @@ -276,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. @@ -351,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) { @@ -412,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) @@ -510,6 +530,33 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { + // EIP1559 guards + if pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && pool.chain.CurrentBlock().BaseFee() == nil { + return ErrNoBaseFee + } + if pool.chainconfig.IsEIP1559Finalized(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return ErrTxNotEIP1559 + } + if !pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return ErrTxIsEIP1559 + } + if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { + return ErrTxSetsLegacyAndEIP1559Fields + } + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return ErrMissingGasFields + } + // Set the gasPrice to the tx.GasPrice() if it is non nil (legacy transaction) + var gasPrice *big.Int + if tx.GasPrice() != nil { + gasPrice = tx.GasPrice() + } else { // Derive the gasPrice from the tx.GasPremium() and tx.FeeCap() (EIP1559 transaction) + gasPrice = new(big.Int).Add(pool.chain.CurrentBlock().BaseFee(), tx.GasPremium()) + if gasPrice.Cmp(tx.FeeCap()) > 0 { + gasPrice.Set(tx.FeeCap()) + } + } + // Heuristic limit, reject transactions over 32KB to prevent DOS attacks if tx.Size() > 32*1024 { return ErrOversizedData @@ -530,7 +577,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { } // Drop non-local transactions under our own minimal accepted gas price local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network - if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 { + if !local && pool.gasPrice.Cmp(gasPrice) > 0 { return ErrUnderpriced } // Ensure the transaction adheres to nonce ordering @@ -539,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. @@ -561,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 { @@ -594,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 @@ -642,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) @@ -687,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) @@ -1011,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) @@ -1173,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) @@ -1365,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/tx_pool_test.go b/core/tx_pool_test.go index 0f1e7ac8f..a78b97912 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) } @@ -73,13 +75,23 @@ func transaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Tr } func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { - tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasprice, nil, nil, nil), types.HomesteadSigner{}, key) + return tx +} + +func eip1559Transaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey, gasPremium, feeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, nil, nil, gasPremium, feeCap), types.HomesteadSigner{}, key) + return tx +} + +func malformedTransaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey, gasPrice, gasPremium, feeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasPrice, nil, gasPremium, feeCap), types.HomesteadSigner{}, key) return tx } func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) - 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,38 +605,238 @@ 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), 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) - if _, err := pool.add(tx, false); err != nil { + 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) } pool.removeTx(tx.Hash(), true) @@ -369,15 +859,15 @@ 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() signer := types.HomesteadSigner{} - tx1, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100000, big.NewInt(1), nil), signer, key) - tx2, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(2), nil), signer, key) - tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(1), nil), signer, key) + tx1, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100000, big.NewInt(1), nil, nil, nil), signer, key) + tx2, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(2), nil, nil, nil), signer, key) + tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(1), 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 { @@ -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 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) +// 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) + } } -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) } - if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { - t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) + 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) + } + 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, int(testTxPoolConfig.AccountQueue+5)); err != nil { - t.Fatalf("event firing failed: %v", err) + 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)) + } + 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) } - // 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])) +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 { + 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(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil { - t.Fatalf("failed to replace original proper queued transaction: %v", err) + 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 := 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) } @@ -1828,3 +5185,67 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) { pool.AddRemotes(batch) } } + +func BenchmarkPoolBatchInsertEIP1559100(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 100) } +func BenchmarkPoolBatchInsertEIP15591000(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 1000) } +func BenchmarkPoolBatchInsertEIP155910000(b *testing.B) { benchmarkPoolBatchInsertEIP1559(b, 10000) } + +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)) + + 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) + } +} + +func BenchmarkPoolBatchInsertEIP1559Finalized100(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 100) +} +func BenchmarkPoolBatchInsertEIP1559Finalized1000(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 1000) +} +func BenchmarkPoolBatchInsertEIP1559Finalized10000(b *testing.B) { + benchmarkPoolBatchInsertEIP1559Finalized(b, 10000) +} + +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) + } +} diff --git a/core/types/block.go b/core/types/block.go index b0ec7fc77..2d600cf2a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -84,6 +84,7 @@ type Header struct { Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + BaseFee *big.Int `json:"baseFee" rlp:"nil"` } // field type overrides for gencodec @@ -94,9 +95,167 @@ type headerMarshaling struct { GasUsed hexutil.Uint64 Time hexutil.Uint64 Extra hexutil.Bytes + BaseFee *hexutil.Big Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON } +// EncodeRLP implements rlp.Encoder +func (h *Header) EncodeRLP(w io.Writer) error { + if h.BaseFee == nil { + return rlp.Encode(w, []interface{}{ + h.ParentHash, + h.UncleHash, + h.Coinbase, + h.Root, + h.TxHash, + h.ReceiptHash, + h.Bloom, + h.Difficulty, + h.Number, + h.GasLimit, + h.GasUsed, + h.Time, + h.Extra, + h.MixDigest, + h.Nonce, + }) + } + return rlp.Encode(w, []interface{}{ + h.ParentHash, + h.UncleHash, + h.Coinbase, + h.Root, + h.TxHash, + h.ReceiptHash, + h.Bloom, + h.Difficulty, + h.Number, + h.GasLimit, + h.GasUsed, + h.Time, + h.Extra, + h.MixDigest, + h.Nonce, + h.BaseFee, + }) +} + +// DecodeRLP implements rlp.Decoder +func (h *Header) DecodeRLP(s *rlp.Stream) error { + _, err := s.List() + if err != nil { + return err + } + parentHash := new(common.Hash) + if err = s.Decode(parentHash); err != nil { + return err + } + uncleHash := new(common.Hash) + if err = s.Decode(uncleHash); err != nil { + return err + } + coinbase := new(common.Address) + if err = s.Decode(coinbase); err != nil { + return err + } + root := new(common.Hash) + if err = s.Decode(root); err != nil { + return err + } + txHash := new(common.Hash) + if err = s.Decode(txHash); err != nil { + return err + } + receiptHash := new(common.Hash) + if err = s.Decode(receiptHash); err != nil { + return err + } + bloom := new(Bloom) + if err = s.Decode(bloom); err != nil { + return err + } + difficulty := new(big.Int) + if err = s.Decode(difficulty); err != nil { + return err + } + number := new(big.Int) + if err = s.Decode(number); err != nil { + return err + } + gasLimit := new(uint64) + if err = s.Decode(gasLimit); err != nil { + return err + } + gasUsed := new(uint64) + if err = s.Decode(gasUsed); err != nil { + return err + } + time := new(uint64) + if err = s.Decode(time); err != nil { + return err + } + extra := new([]byte) + if err = s.Decode(extra); err != nil { + return err + } + mixDigest := new(common.Hash) + if err = s.Decode(mixDigest); err != nil { + return err + } + nonce := new(BlockNonce) + if err = s.Decode(nonce); err != nil { + return err + } + // if this is the end of the list then we are decoding a legacy header + if err = s.ListEnd(); err == nil { + h.ParentHash = *parentHash + h.UncleHash = *uncleHash + h.Coinbase = *coinbase + h.Root = *root + h.TxHash = *txHash + h.ReceiptHash = *receiptHash + h.Bloom = *bloom + h.Difficulty = difficulty + h.Number = number + h.GasLimit = *gasLimit + h.GasUsed = *gasUsed + h.Time = *time + h.Extra = *extra + h.MixDigest = *mixDigest + h.Nonce = *nonce + return nil + } + // if we are not at the end of the list, continue decoding the 1559 header fields + if err != rlp.ErrNotAtEOL { + return err + } + baseFee := new(big.Int) + if err = s.Decode(baseFee); err != nil { + return err + } + // we should now be at the end of the list even for a 1559 transaction + if err = s.ListEnd(); err != nil { + return err + } + h.ParentHash = *parentHash + h.UncleHash = *uncleHash + h.Coinbase = *coinbase + h.Root = *root + h.TxHash = *txHash + h.ReceiptHash = *receiptHash + h.Bloom = *bloom + h.Difficulty = difficulty + h.Number = number + h.GasLimit = *gasLimit + h.GasUsed = *gasUsed + h.Time = *time + h.Extra = *extra + h.MixDigest = *mixDigest + h.Nonce = *nonce + h.BaseFee = baseFee + return nil +} + // Hash returns the block hash of the header, which is simply the keccak256 hash of its // RLP encoding. func (h *Header) Hash() common.Hash { @@ -318,6 +477,7 @@ func (b *Block) TxHash() common.Hash { return b.header.TxHash } func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash } func (b *Block) UncleHash() common.Hash { return b.header.UncleHash } func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) } +func (b *Block) BaseFee() *big.Int { return b.header.BaseFee } func (b *Block) Header() *Header { return CopyHeader(b.header) } diff --git a/core/types/block_test.go b/core/types/block_test.go index ff0a641e5..9436af125 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -18,6 +18,11 @@ package types import ( "bytes" + + "github.com/ethereum/go-ethereum/params" + + //"fmt" + //"github.com/ethereum/go-ethereum/params" "math/big" "reflect" "testing" @@ -50,7 +55,7 @@ func TestBlockEncoding(t *testing.T) { check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), common.StorageSize(len(blockEnc))) - tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) + tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil, nil, nil) tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) check("len(Transactions)", len(block.Transactions()), 1) check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) @@ -64,6 +69,80 @@ func TestBlockEncoding(t *testing.T) { } } +func TestEIP1559BlockEncoding(t *testing.T) { + tx, err := decodeTx(common.Hex2Bytes("f86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ba0612a9c962f0ac2841c671c021e45aeaa23f2892bf34da5d32d7948754cf078bda03a350e0e4e1ff5299228eb921af7c0435dbabd5b3d17f79c925864192ca9d126")) + if err != nil { + t.Fatal(err) + } + txs := Transactions{tx} + header := &Header{ + ParentHash: common.HexToHash("0x"), + Coinbase: common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"), + Root: common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"), + Difficulty: big.NewInt(131072), + Number: big.NewInt(2675001), + GasLimit: 3141592, + GasUsed: 21000, + Time: 1426516743, + MixDigest: common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"), + Nonce: EncodeNonce(11617697748499542468), + BaseFee: big.NewInt(800000), + } + hash := header.Hash() + rct := NewReceipt([]byte{0}, false, 800000) + rcts := Receipts{rct} + rcts.DeriveFields(params.MainnetChainConfig, hash, 2675001, txs) + block := NewBlock(header, txs, nil, rcts) + blockBytes, err := rlp.EncodeToBytes(block) + if err != nil { + t.Fatal(err) + } + expected := common.FromHex("f90271f90200a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0ed048395d4a0847b0cecfa3f649a9f4e56fd9813cb1bae507fb8a5a8a45009c6a0e596313d377d2ebcd789a5a17bbd6c6cb458be3f3c896db1e307865aa33acea2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200008328d139832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4830c3500f86bf86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ba0612a9c962f0ac2841c671c021e45aeaa23f2892bf34da5d32d7948754cf078bda03a350e0e4e1ff5299228eb921af7c0435dbabd5b3d17f79c925864192ca9d126c0") + if !bytes.Equal(blockBytes, expected) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", blockBytes, expected) + } +} + +func TestEIP1559BlockDecoding(t *testing.T) { + blockEnc := common.FromHex("f90271f90200a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0ed048395d4a0847b0cecfa3f649a9f4e56fd9813cb1bae507fb8a5a8a45009c6a0e596313d377d2ebcd789a5a17bbd6c6cb458be3f3c896db1e307865aa33acea2b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200008328d139832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4830c3500f86bf86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ba0612a9c962f0ac2841c671c021e45aeaa23f2892bf34da5d32d7948754cf078bda03a350e0e4e1ff5299228eb921af7c0435dbabd5b3d17f79c925864192ca9d126c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(21000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Hash", block.Hash(), common.HexToHash("112545e5bd6c278a01e46061467f23b9e690685d28df917a5ad184171b095770")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), common.StorageSize(len(blockEnc))) + check("BaseFee", block.BaseFee(), big.NewInt(800000)) + + tx1, err := decodeTx(common.Hex2Bytes("f86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ba0612a9c962f0ac2841c671c021e45aeaa23f2892bf34da5d32d7948754cf078bda03a350e0e4e1ff5299228eb921af7c0435dbabd5b3d17f79c925864192ca9d126")) + if err != nil { + t.Fatal(err) + } + check("len(Transactions)", len(block.Transactions()), 1) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + func TestUncleHash(t *testing.T) { uncles := make([]*Header, 0) h := CalcUncleHash(uncles) diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 4212b8d94..348079cd8 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -31,6 +31,7 @@ func (h Header) MarshalJSON() ([]byte, error) { Extra hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFee" rlp:"nil"` Hash common.Hash `json:"hash"` } var enc Header @@ -49,6 +50,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.Extra = h.Extra enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce + enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -71,6 +73,7 @@ func (h *Header) UnmarshalJSON(input []byte) error { Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest *common.Hash `json:"mixHash"` Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFee" rlp:"nil"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -134,5 +137,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.Nonce != nil { h.Nonce = *dec.Nonce } + if dec.BaseFee != nil { + h.BaseFee = (*big.Int)(dec.BaseFee) + } return nil } diff --git a/core/types/gen_tx_json.go b/core/types/gen_tx_json.go index e676058ec..1778180e7 100644 --- a/core/types/gen_tx_json.go +++ b/core/types/gen_tx_json.go @@ -17,11 +17,13 @@ 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"` Payload hexutil.Bytes `json:"input" gencodec:"required"` + GasPremium *hexutil.Big `json:"gasPremium" rlp:"nil"` + FeeCap *hexutil.Big `json:"feeCap" rlp:"nil"` V *hexutil.Big `json:"v" gencodec:"required"` R *hexutil.Big `json:"r" gencodec:"required"` S *hexutil.Big `json:"s" gencodec:"required"` @@ -34,6 +36,8 @@ func (t txdata) MarshalJSON() ([]byte, error) { enc.Recipient = t.Recipient enc.Amount = (*hexutil.Big)(t.Amount) enc.Payload = t.Payload + enc.GasPremium = (*hexutil.Big)(t.GasPremium) + enc.FeeCap = (*hexutil.Big)(t.FeeCap) enc.V = (*hexutil.Big)(t.V) enc.R = (*hexutil.Big)(t.R) enc.S = (*hexutil.Big)(t.S) @@ -45,11 +49,13 @@ 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"` Payload *hexutil.Bytes `json:"input" gencodec:"required"` + GasPremium *hexutil.Big `json:"gasPremium" rlp:"nil"` + FeeCap *hexutil.Big `json:"feeCap" rlp:"nil"` V *hexutil.Big `json:"v" gencodec:"required"` R *hexutil.Big `json:"r" gencodec:"required"` S *hexutil.Big `json:"s" gencodec:"required"` @@ -63,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") } @@ -82,6 +87,12 @@ func (t *txdata) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'input' for txdata") } t.Payload = *dec.Payload + if dec.GasPremium != nil { + t.GasPremium = (*big.Int)(dec.GasPremium) + } + if dec.FeeCap != nil { + t.FeeCap = (*big.Int)(dec.FeeCap) + } if dec.V == nil { return errors.New("missing required field 'v' for txdata") } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 806b3dd2a..7b09946af 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -48,7 +48,7 @@ func TestLegacyReceiptDecoding(t *testing.T) { }, } - tx := NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) + tx := NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil, nil, nil) receipt := &Receipt{ Status: ReceiptStatusFailed, CumulativeGasUsed: 1, @@ -155,8 +155,8 @@ func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) { func TestDeriveFields(t *testing.T) { // Create a few transactions to have receipts for txs := Transactions{ - NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), - NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil, nil, nil), + NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil, nil, nil), } // Create the corresponding receipts receipts := Receipts{ diff --git a/core/types/transaction.go b/core/types/transaction.go index 3eb8df0ac..651d12c14 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,12 +45,16 @@ type Transaction struct { type txdata struct { AccountNonce uint64 `json:"nonce" gencodec:"required"` - Price *big.Int `json:"gasPrice" gencodec:"required"` + 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"` Payload []byte `json:"input" gencodec:"required"` + // EIP1559 gas values + GasPremium *big.Int `json:"gasPremium" rlp:"nil"` // nil means legacy transaction + FeeCap *big.Int `json:"feeCap" rlp:"nil"` // nil means legacy transaction + // Signature values V *big.Int `json:"v" gencodec:"required"` R *big.Int `json:"r" gencodec:"required"` @@ -66,20 +70,22 @@ type txdataMarshaling struct { GasLimit hexutil.Uint64 Amount *hexutil.Big Payload hexutil.Bytes + GasPremium *hexutil.Big + FeeCap *hexutil.Big V *hexutil.Big R *hexutil.Big S *hexutil.Big } -func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data) +func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, gasPremium, feeCap *big.Int) *Transaction { + return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data, gasPremium, feeCap) } -func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data) +func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, gasPremium, feeCap *big.Int) *Transaction { + return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data, gasPremium, feeCap) } -func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { +func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, gasPremium, feeCap *big.Int) *Transaction { if len(data) > 0 { data = common.CopyBytes(data) } @@ -89,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), @@ -98,7 +103,13 @@ 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 + } + if feeCap != nil { + d.FeeCap = feeCap } return &Transaction{data: d} @@ -125,18 +136,127 @@ func isProtectedV(V *big.Int) bool { // EncodeRLP implements rlp.Encoder func (tx *Transaction) EncodeRLP(w io.Writer) error { + if tx.data.FeeCap == nil || tx.data.GasPremium == nil { + return rlp.Encode(w, []interface{}{ + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Recipient, + tx.data.Amount, + tx.data.Payload, + tx.data.V, + tx.data.R, + tx.data.S, + }) + } return rlp.Encode(w, &tx.data) } // DecodeRLP implements rlp.Decoder -func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - err := s.Decode(&tx.data) - if err == nil { +func (tx *Transaction) DecodeRLP(stream *rlp.Stream) error { + size, err := stream.List() + if err != nil { + return err + } + accountNonce := new(uint64) + if err = stream.Decode(accountNonce); err != nil { + return err + } + price := new(big.Int) + if err = stream.Decode(price); err != nil { + return err + } + gasLimit := new(uint64) + if err = stream.Decode(gasLimit); err != nil { + return err + } + _, recipientSize, err := stream.Kind() + if err != nil { + return err + } + var recipient *common.Address + // the below is to handle the "rlp: nil" tag (tag itself is not needed anymore because of this manual handling) + // attempting to unpack a zero value into *common.Address throws an error + // if there is a non-zero address, unpack it + if recipientSize != 0 { + recipient = new(common.Address) + if err = stream.Decode(recipient); err != nil { + return err + } + } else { + // otherwise if the value is of size zero throw away the value, move to next value in the stream, and leave recipient nil + if _, err = stream.Raw(); err != nil { + return err + } + } + amount := new(big.Int) + if err = stream.Decode(amount); err != nil { + return err + } + payload := new([]byte) + if err = stream.Decode(payload); err != nil { + return err + } + gasPremium := new(big.Int) + if err = stream.Decode(gasPremium); err != nil { + return err + } + feeCap := new(big.Int) + if err = stream.Decode(feeCap); err != nil { + return err + } + v := new(big.Int) + if err = stream.Decode(v); err != nil { + return err + } + // if this is the end of the list then we are decoding a legacy transaction + // so the decoded gasPremium, feeCap, and v values are shifted into the v, r, and s values + if err = stream.ListEnd(); err == nil { + tx.data = txdata{ + AccountNonce: *accountNonce, + Price: price, + GasLimit: *gasLimit, + Recipient: recipient, + Amount: amount, + Payload: *payload, + V: gasPremium, + R: feeCap, + S: v, + } tx.size.Store(common.StorageSize(rlp.ListSize(size))) + return nil } - - return err + // if we are not at the end of the list, continue decoding the 1559 transaction fields + if err != rlp.ErrNotAtEOL { + return err + } + r := new(big.Int) + if err := stream.Decode(r); err != nil { + return err + } + s := new(big.Int) + if err := stream.Decode(s); err != nil { + return err + } + // we should now be at the end of the list for a EIP1559 transaction + if err = stream.ListEnd(); err != nil { + return err + } + tx.data = txdata{ + AccountNonce: *accountNonce, + Price: nil, + GasLimit: *gasLimit, + Recipient: recipient, + Amount: amount, + Payload: *payload, + GasPremium: gasPremium, + FeeCap: feeCap, + V: v, + R: r, + S: s, + } + tx.size.Store(common.StorageSize(rlp.ListSize(size))) + return nil } // MarshalJSON encodes the web3 RPC transaction format. @@ -172,12 +292,14 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return nil } -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) 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 } +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 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 } +func (tx *Transaction) GasPremium() *big.Int { return tx.data.GasPremium } +func (tx *Transaction) FeeCap() *big.Int { return tx.data.FeeCap } // To returns the recipient address of the transaction. // It returns nil if the transaction is a contract creation. @@ -221,11 +343,13 @@ 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, checkNonce: true, + gasPremium: tx.data.GasPremium, + feeCap: tx.data.FeeCap, } var err error @@ -246,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. @@ -394,9 +530,12 @@ type Message struct { gasPrice *big.Int data []byte checkNonce bool + gasPremium *big.Int + feeCap *big.Int } -func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message { +// NewMessage creates and returns a new message +func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool, gasPremium, feeCap *big.Int) Message { return Message{ from: from, to: to, @@ -406,6 +545,8 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b gasPrice: gasPrice, data: data, checkNonce: checkNonce, + gasPremium: gasPremium, + feeCap: feeCap, } } @@ -417,3 +558,5 @@ func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Data() []byte { return m.data } func (m Message) CheckNonce() bool { return m.checkNonce } +func (m Message) GasPremium() *big.Int { return m.gasPremium } +func (m Message) FeeCap() *big.Int { return m.feeCap } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 842fedbd0..fdeb7e0e3 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -153,6 +153,17 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s EIP155Signer) Hash(tx *Transaction) common.Hash { + if tx.data.GasPremium == nil && tx.data.FeeCap == nil { + return rlpHash([]interface{}{ + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Recipient, + tx.data.Amount, + tx.data.Payload, + s.chainId, uint(0), uint(0), + }) + } return rlpHash([]interface{}{ tx.data.AccountNonce, tx.data.Price, @@ -160,6 +171,8 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash { tx.data.Recipient, tx.data.Amount, tx.data.Payload, + tx.data.GasPremium, + tx.data.FeeCap, s.chainId, uint(0), uint(0), }) } @@ -205,6 +218,16 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v * // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { + if tx.data.GasPremium == nil && tx.data.FeeCap == nil { + return rlpHash([]interface{}{ + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Recipient, + tx.data.Amount, + tx.data.Payload, + }) + } return rlpHash([]interface{}{ tx.data.AccountNonce, tx.data.Price, @@ -212,6 +235,8 @@ func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { tx.data.Recipient, tx.data.Amount, tx.data.Payload, + tx.data.GasPremium, + tx.data.FeeCap, }) } diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go index 689fc38a9..0ecdcc4d0 100644 --- a/core/types/transaction_signing_test.go +++ b/core/types/transaction_signing_test.go @@ -30,7 +30,7 @@ func TestEIP155Signing(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) signer := NewEIP155Signer(big.NewInt(18)) - tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil, nil, nil), signer, key) if err != nil { t.Fatal(err) } @@ -49,7 +49,7 @@ func TestEIP155ChainId(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) signer := NewEIP155Signer(big.NewInt(18)) - tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil, nil, nil), signer, key) if err != nil { t.Fatal(err) } @@ -61,7 +61,7 @@ func TestEIP155ChainId(t *testing.T) { t.Error("expected chainId to be", signer.chainId, "got", tx.ChainId()) } - tx = NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + tx = NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil, nil, nil) tx, err = SignTx(tx, HomesteadSigner{}, key) if err != nil { t.Fatal(err) @@ -118,7 +118,7 @@ func TestEIP155SigningVitalik(t *testing.T) { func TestChainId(t *testing.T) { key, _ := defaultTestKey() - tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil) + tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil, nil, nil) var err error tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(1)), key) diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 7a1b6cd4d..747644292 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/json" + "log" "math/big" "testing" @@ -36,6 +37,8 @@ var ( common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(0), 0, big.NewInt(0), nil, + nil, + nil, ) rightvrsTx, _ = NewTransaction( @@ -45,19 +48,80 @@ var ( 2000, big.NewInt(1), common.FromHex("5544"), + nil, + nil, ).WithSignature( HomesteadSigner{}, common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), ) + + noRctNoSigTx = NewContractCreation( + 3, + big.NewInt(10), + 2000, + big.NewInt(1), + common.FromHex("5544"), + nil, + nil, + ) + + noSignatureTx = NewTransaction( + 3, + common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + big.NewInt(10), + 2000, + big.NewInt(1), + common.FromHex("5544"), + nil, + nil, + ) + + eip1559Tx, _ = NewTransaction( + 3, + common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + big.NewInt(10), + 2000, + nil, + common.FromHex("5544"), + big.NewInt(200000), + big.NewInt(800000), + ).WithSignature( + HomesteadSigner{}, + common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), + ) + + eip1559NoRctNoSigTx = NewContractCreation( + 3, + big.NewInt(10), + 2000, + nil, + common.FromHex("5544"), + big.NewInt(200000), + big.NewInt(800000), + ) + + eip1559NoSignatureTx = NewTransaction( + 3, + common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + big.NewInt(10), + 2000, + nil, + common.FromHex("5544"), + big.NewInt(200000), + big.NewInt(800000), + ) ) func TestTransactionSigHash(t *testing.T) { var homestead HomesteadSigner if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { - t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash()) + t.Errorf("empty transaction hash mismatch, got %x", homestead.Hash(emptyTx)) } if homestead.Hash(rightvrsTx) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { - t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash()) + t.Errorf("RightVRS transaction hash mismatch, got %x", homestead.Hash(rightvrsTx)) + } + if homestead.Hash(eip1559Tx) != common.HexToHash("33bf0422a1819f7b82ae6cba37ae0169a37ec05ccb6d5a9963fe48cf765fe97f") { + t.Errorf("eip1559Tx transaction hash mismatch, got %x", homestead.Hash(eip1559Tx)) } } @@ -72,6 +136,60 @@ func TestTransactionEncode(t *testing.T) { } } +func TestEIP1159TransactionEncode(t *testing.T) { + tx1559b, err := rlp.EncodeToBytes(eip1559Tx) + if err != nil { + t.Fatalf("EIP1559 tx encode error: %v", err) + } + tx1559bshould := common.FromHex("f86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3") + if !bytes.Equal(tx1559b, tx1559bshould) { + t.Errorf("EIP1559 tx encoded RLP mismatch, got %x", tx1559b) + } +} + +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) @@ -86,12 +204,39 @@ func defaultTestKey() (*ecdsa.PrivateKey, common.Address) { } func TestRecipientEmpty(t *testing.T) { - _, addr := defaultTestKey() + key, addr := defaultTestKey() tx, err := decodeTx(common.Hex2Bytes("f8498080808080011ca09b16de9d5bdee2cf56c28d16275a4da68cd30273e2525f3959f5d62557489921a0372ebd8fb3345f7db7b5a86d42e24d36e983e259b0664ceb8c227ec9af572f3d")) if err != nil { t.Fatal(err) } + from, err := Sender(HomesteadSigner{}, tx) + if err != nil { + t.Fatal(err) + } + if addr != from { + t.Fatal("derived address doesn't match") + } + + tx2, err := SignTx(noRctNoSigTx, HomesteadSigner{}, key) + if err != nil { + log.Fatal(err) + } + from2, err := Sender(HomesteadSigner{}, tx2) + if err != nil { + t.Fatal(err) + } + if addr != from2 { + t.Fatal("derived address doesn't match") + } +} + +func TestEIP1559RecipientEmpty(t *testing.T) { + key, addr := defaultTestKey() + tx, err := decodeTx(common.Hex2Bytes("f85503808207d0800a82554483030d40830c35001ca09de1afa53c1d66d6d759d28c1f2f48e3073de340e0b0966bd3b72e86c61dc40ca0120a40bca942f11fc1c00ac52f499acb006dd7cb70e3dbf41bae82e4fbf373c3")) + if err != nil { + t.Fatal(err) + } from, err := Sender(HomesteadSigner{}, tx) if err != nil { t.Fatal(err) @@ -99,16 +244,55 @@ func TestRecipientEmpty(t *testing.T) { if addr != from { t.Fatal("derived address doesn't match") } + + tx2, err := SignTx(eip1559NoRctNoSigTx, HomesteadSigner{}, key) + if err != nil { + log.Fatal(err) + } + from2, err := Sender(HomesteadSigner{}, tx2) + if err != nil { + t.Fatal(err) + } + if addr != from2 { + t.Fatal("derived address doesn't match") + } } func TestRecipientNormal(t *testing.T) { - _, addr := defaultTestKey() + key, addr := defaultTestKey() tx, err := decodeTx(common.Hex2Bytes("f85d80808094000000000000000000000000000000000000000080011ca0527c0d8f5c63f7b9f41324a7c8a563ee1190bcbf0dac8ab446291bdbf32f5c79a0552c4ef0a09a04395074dab9ed34d3fbfb843c2f2546cc30fe89ec143ca94ca6")) if err != nil { t.Fatal(err) } + from, err := Sender(HomesteadSigner{}, tx) + if err != nil { + t.Fatal(err) + } + if addr != from { + t.Fatal("derived address doesn't match") + } + tx2, err := SignTx(noSignatureTx, HomesteadSigner{}, key) + if err != nil { + log.Fatal(err) + } + from2, err := Sender(HomesteadSigner{}, tx2) + if err != nil { + t.Fatal(err) + } + if addr != from2 { + t.Fatal("derived address doesn't match") + } +} + +func TestEIP1559RecipientNormal(t *testing.T) { + key, addr := defaultTestKey() + + tx, err := decodeTx(common.Hex2Bytes("f86903808207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a82554483030d40830c35001ba0612a9c962f0ac2841c671c021e45aeaa23f2892bf34da5d32d7948754cf078bda03a350e0e4e1ff5299228eb921af7c0435dbabd5b3d17f79c925864192ca9d126")) + if err != nil { + t.Fatal(err) + } from, err := Sender(HomesteadSigner{}, tx) if err != nil { t.Fatal(err) @@ -116,6 +300,18 @@ func TestRecipientNormal(t *testing.T) { if addr != from { t.Fatal("derived address doesn't match") } + + tx2, err := SignTx(eip1559NoSignatureTx, HomesteadSigner{}, key) + if err != nil { + log.Fatal(err) + } + from2, err := Sender(HomesteadSigner{}, tx2) + if err != nil { + t.Fatal(err) + } + if addr != from2 { + t.Fatal("derived address doesn't match") + } } // Tests that transactions can be correctly sorted according to their price in @@ -134,7 +330,7 @@ func TestTransactionPriceNonceSort(t *testing.T) { for start, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) for i := 0; i < 25; i++ { - tx, _ := SignTx(NewTransaction(uint64(start+i), common.Address{}, big.NewInt(100), 100, big.NewInt(int64(start+i)), nil), signer, key) + tx, _ := SignTx(NewTransaction(uint64(start+i), common.Address{}, big.NewInt(100), 100, big.NewInt(int64(start+i)), nil, nil, nil), signer, key) groups[addr] = append(groups[addr], tx) } } @@ -185,9 +381,57 @@ func TestTransactionJSON(t *testing.T) { var tx *Transaction switch i % 2 { case 0: - tx = NewTransaction(i, common.Address{1}, common.Big0, 1, common.Big2, []byte("abcdef")) + tx = NewTransaction(i, common.Address{1}, common.Big0, 1, common.Big2, []byte("abcdef"), nil, nil) + case 1: + tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef"), nil, nil) + } + transactions = append(transactions, tx) + + signedTx, err := SignTx(tx, signer, key) + if err != nil { + t.Fatalf("could not sign transaction: %v", err) + } + + transactions = append(transactions, signedTx) + } + + for _, tx := range transactions { + data, err := json.Marshal(tx) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + + var parsedTx *Transaction + if err := json.Unmarshal(data, &parsedTx); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + + // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S + if tx.Hash() != parsedTx.Hash() { + t.Errorf("parsed tx differs from original tx, want %v, got %v", tx, parsedTx) + } + if tx.ChainId().Cmp(parsedTx.ChainId()) != 0 { + t.Errorf("invalid chain id, want %d, got %d", tx.ChainId(), parsedTx.ChainId()) + } + } +} + +// TestEIP1559TransactionJSON tests serializing/de-serializing to/from JSON. +func TestEIP1559TransactionJSON(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("could not generate key: %v", err) + } + signer := NewEIP155Signer(common.Big1) + + transactions := make([]*Transaction, 0, 50) + for i := uint64(0); i < 25; i++ { + var tx *Transaction + switch i % 2 { + case 0: + tx = NewTransaction(i, common.Address{1}, common.Big0, 1, nil, []byte("abcdef"), big.NewInt(200000), big.NewInt(800000)) case 1: - tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef")) + tx = NewContractCreation(i, common.Big0, 1, nil, []byte("abcdef"), big.NewInt(200000), big.NewInt(800000)) } transactions = append(transactions, tx) diff --git a/core/vm/evm.go b/core/vm/evm.go index 751c1fdc1..5926e452a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -91,6 +91,7 @@ type Context struct { BlockNumber *big.Int // Provides information for NUMBER Time *big.Int // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE } // EVM is the Ethereum Virtual Machine base object and provides diff --git a/dashboard/assets/yarn.lock b/dashboard/assets/yarn.lock index 0fdf5593c..62da431c4 100644 --- a/dashboard/assets/yarn.lock +++ b/dashboard/assets/yarn.lock @@ -1831,11 +1831,16 @@ colors@^1.3.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== -commander@^2.11.0, commander@^2.15.1, commander@^2.19.0, commander@~2.20.0: +commander@^2.11.0, commander@^2.15.1, commander@^2.19.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@~2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3430,9 +3435,9 @@ handle-thing@^2.0.0: integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== handlebars@^4.0.11: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== + version "4.5.3" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482" + integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA== dependencies: neo-async "^2.6.0" optimist "^0.6.1" @@ -6809,11 +6814,11 @@ ua-parser-js@^0.7.18: integrity sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw== uglify-js@^3.1.4: - version "3.6.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" - integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== + version "3.7.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a" + integrity sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg== dependencies: - commander "~2.20.0" + commander "~2.20.3" source-map "~0.6.1" unicode-canonical-property-names-ecmascript@^1.0.4: diff --git a/eth/api_tracer.go b/eth/api_tracer.go index ce211cbd9..c54f2bee4 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -491,6 +491,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, } }() } + var gp1559 *core.GasPool // Feed the transactions into the tracers and return var failed error for i, tx := range txs { @@ -501,8 +502,11 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, msg, _ := tx.AsMessage(signer) vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + if api.eth.blockchain.Config().IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(msg.Gas()) + } vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), gp1559); err != nil { failed = err break } @@ -563,6 +567,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block var ( signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string + gp1559 *core.GasPool ) for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution @@ -596,7 +601,10 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) - _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + if api.eth.blockchain.Config().IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(msg.Gas()) + } + _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), gp1559) if writer != nil { writer.Flush() } @@ -727,7 +735,11 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v var ( tracer vm.Tracer err error + gp1559 *core.GasPool ) + if api.eth.blockchain.Config().IsEIP1559(vmctx.BlockNumber) { + gp1559 = new(core.GasPool).AddGas(message.Gas()) + } switch { case config != nil && config.Tracer != nil: // Define a meaningful timeout of a single transaction trace @@ -758,7 +770,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) - ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) + ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), gp1559) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } @@ -810,9 +822,13 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree if idx == txIndex { return msg, context, statedb, nil } + var gp1559 *core.GasPool + if api.eth.blockchain.Config().IsEIP1559(block.Number()) { + gp1559 = new(core.GasPool).AddGas(tx.Gas()) + } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), gp1559); err != nil { return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index f410152f5..6b56ce76f 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), 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 83172c534..162086d9c 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), 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/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 93cb43123..a71655c7f 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -227,11 +227,11 @@ func TestPendingTxFilter(t *testing.T) { api = NewPublicFilterAPI(backend, false) transactions = []*types.Transaction{ - types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), - types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), - types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), - types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), - types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil, nil, nil), + types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil, nil, nil), + types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil, nil, nil), + types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil, nil, nil), + types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil, nil, nil), } hashes []common.Hash diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index eafa19cdf..15852cf36 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -138,7 +138,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)) + gen.AddUncheckedTx(types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil, nil, nil)) case 2: receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{ @@ -148,7 +148,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)) + gen.AddUncheckedTx(types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil, nil, nil)) case 998: receipt := types.NewReceipt(nil, false, 0) @@ -159,7 +159,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, big.NewInt(998), nil)) + gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, big.NewInt(998), nil, nil, nil)) case 999: receipt := types.NewReceipt(nil, false, 0) receipt.Logs = []*types.Log{ @@ -169,7 +169,7 @@ func TestFilters(t *testing.T) { }, } gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, big.NewInt(999), nil)) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, big.NewInt(999), nil, nil, nil)) } }) for i, block := range chain { diff --git a/eth/handler_test.go b/eth/handler_test.go index 256883d1f..d43286353 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), 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), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, 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), 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), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, 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/eth/helper_test.go b/eth/helper_test.go index e66910334..fdac3fb62 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -132,7 +132,7 @@ func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subs // newTestTransaction create a new dummy transaction. func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction { - tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize)) + tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize), nil, nil) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, from) return tx } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 69eb80a5c..469660592 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -110,6 +110,7 @@ type callContext struct { Time math.HexOrDecimal64 `json:"timestamp"` GasLimit math.HexOrDecimal64 `json:"gasLimit"` Miner common.Address `json:"miner"` + BaseFee *math.HexOrDecimal256 `json:"baseFee"` } // callTracerTest defines a single test to check the call tracer against. @@ -122,7 +123,7 @@ type callTracerTest struct { func TestPrestateTracerCreate2(t *testing.T) { unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"), - new(big.Int), 5000000, big.NewInt(1), []byte{}) + new(big.Int), 5000000, big.NewInt(1), []byte{}, nil, nil) privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) if err != nil { @@ -181,7 +182,7 @@ func TestPrestateTracerCreate2(t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), nil) if _, _, _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } @@ -241,6 +242,7 @@ func TestCallTracer(t *testing.T) { Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), GasPrice: tx.GasPrice(), + BaseFee: (*big.Int)(test.Context.BaseFee), } statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) @@ -255,7 +257,11 @@ func TestCallTracer(t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + var gp1559 *core.GasPool + if test.Genesis.Config.IsEIP1559(context.BlockNumber) { + gp1559 = new(core.GasPool).AddGas(tx.Gas()) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), gp1559) if _, _, _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 0a6f73ab5..1daf030a2 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -534,5 +534,11 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if msg.GasPrice != nil { arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) } + if msg.GasPremium != nil { + arg["gasPremium"] = (*hexutil.Big)(msg.GasPremium) + } + if msg.FeeCap != nil { + arg["feeCap"] = (*hexutil.Big)(msg.FeeCap) + } return arg } diff --git a/interfaces.go b/interfaces.go index 1ff31f96b..14a32a96f 100644 --- a/interfaces.go +++ b/interfaces.go @@ -113,12 +113,14 @@ type ChainSyncReader interface { // CallMsg contains parameters for contract calls. type CallMsg struct { - From common.Address // the sender of the 'transaction' - To *common.Address // the destination contract (nil for contract creation) - Gas uint64 // if 0, the call executes with near-infinite gas - GasPrice *big.Int // wei <-> gas exchange ratio - Value *big.Int // amount of wei sent along with the call - Data []byte // input data, usually an ABI-encoded contract method invocation + From common.Address // the sender of the 'transaction' + To *common.Address // the destination contract (nil for contract creation) + Gas uint64 // if 0, the call executes with near-infinite gas + GasPrice *big.Int // wei <-> gas exchange ratio + Value *big.Int // amount of wei sent along with the call + Data []byte // input data, usually an ABI-encoded contract method invocation + GasPremium *big.Int // EIP1559 gas premium paid to miners (excess of the basefee) + FeeCap *big.Int // Max amount of gas we can use for this trx execution } // A ContractCaller provides contract calls, essentially transactions that are executed by diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ea7bb7fc8..8fc6d4050 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -735,12 +735,14 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A // CallArgs represents the arguments for a call. type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` + GasPremium *hexutil.Big `json:"gasPremium"` + FeeCap *hexutil.Big `json:"feeCap"` } // account indicates the overriding fields of account during the execution of @@ -760,6 +762,23 @@ type account struct { func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + // EIP1559 guards + if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) { + return nil, 0, false, core.ErrTxNotEIP1559 + } + if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) { + return nil, 0, false, core.ErrTxIsEIP1559 + } + if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) { + return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields + } + if args.FeeCap != nil && args.GasPremium == nil { + return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set") + } + if args.GasPremium != nil && args.FeeCap == nil { + return nil, 0, false, errors.New("if GasPremium is set, FeeCap must be set") + } + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, 0, false, err @@ -812,9 +831,13 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) gas = globalGasCap.Uint64() } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() + + var gasPrice *big.Int + if args.GasPremium == nil { + gasPrice = new(big.Int).SetUint64(defaultGasPrice) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } } value := new(big.Int) @@ -828,7 +851,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo } // Create new call message - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, (*big.Int)(args.GasPremium), (*big.Int)(args.FeeCap)) // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. @@ -857,7 +880,11 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Setup the gas pool (also for unmetered requests) // and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - res, gas, failed, err := core.ApplyMessage(evm, msg, gp) + var gp1559 *core.GasPool + if evm.ChainConfig().IsEIP1559(header.Number) { + gp1559 = new(core.GasPool).AddGas(math.MaxUint64) + } + res, gas, failed, err := core.ApplyMessage(evm, msg, gp, gp1559) if err := vmError(); err != nil { return nil, 0, false, err } @@ -1022,6 +1049,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "timestamp": hexutil.Uint64(head.Time), "transactionsRoot": head.TxHash, "receiptsRoot": head.ReceiptHash, + "baseFee": (*hexutil.Big)(head.BaseFee), } } @@ -1093,6 +1121,8 @@ type RPCTransaction struct { To *common.Address `json:"to"` TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` Value *hexutil.Big `json:"value"` + GasPremium *hexutil.Big `json:"gasPremium"` + FeeCap *hexutil.Big `json:"feeCap"` V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` @@ -1109,17 +1139,19 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber v, r, s := tx.RawSignatureValues() result := &RPCTransaction{ - From: from, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), + From: from, + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data()), + Nonce: hexutil.Uint64(tx.Nonce()), + To: tx.To(), + Value: (*hexutil.Big)(tx.Value()), + GasPremium: (*hexutil.Big)(tx.GasPremium()), + FeeCap: (*hexutil.Big)(tx.FeeCap()), + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), } if blockHash != (common.Hash{}) { result.BlockHash = &blockHash @@ -1355,11 +1387,31 @@ type SendTxArgs struct { // newer name and should be preferred by clients. Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input"` + // EIP1559 fields + GasPremium *hexutil.Big `json:"gasPremium"` + FeeCap *hexutil.Big `json:"feeCap"` } // setDefaults is a helper function that fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { - if args.GasPrice == nil { + // EIP1559 guards + if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) { + return core.ErrTxNotEIP1559 + } + if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) { + return core.ErrTxIsEIP1559 + } + if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) { + return core.ErrTxSetsLegacyAndEIP1559Fields + } + if args.FeeCap != nil && args.GasPremium == nil { + return errors.New("if FeeCap is set, GasPremium must be set") + } + if args.GasPremium != nil && args.FeeCap == nil { + return errors.New("if GasPremium is set, FeeCap must be set") + } + // If EIP1559 is activated but not finalized and neither a GasPrice, GasPremium, or FeeCap are provided default to suggesting a GasPrice + if args.GasPrice == nil && args.GasPremium == nil { price, err := b.SuggestPrice(ctx) if err != nil { return err @@ -1400,11 +1452,13 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { input = args.Data } callArgs := CallArgs{ - From: &args.From, // From shouldn't be nil - To: args.To, - GasPrice: args.GasPrice, - Value: args.Value, - Data: input, + From: &args.From, // From shouldn't be nil + To: args.To, + GasPrice: args.GasPrice, + Value: args.Value, + Data: input, + GasPremium: args.GasPremium, + FeeCap: args.FeeCap, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) @@ -1425,9 +1479,9 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { input = *args.Data } if args.To == nil { - return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input, (*big.Int)(args.GasPremium), (*big.Int)(args.FeeCap)) } - return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input, (*big.Int)(args.GasPremium), (*big.Int)(args.FeeCap)) } // SubmitTransaction is a helper function that submits tx to txPool and logs a message. @@ -1504,6 +1558,19 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod if err := rlp.DecodeBytes(encodedTx, tx); err != nil { return common.Hash{}, err } + // EIP1559 guards + if s.b.ChainConfig().IsEIP1559Finalized(s.b.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) { + return common.Hash{}, core.ErrTxNotEIP1559 + } + if !s.b.ChainConfig().IsEIP1559(s.b.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) { + return common.Hash{}, core.ErrTxIsEIP1559 + } + if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) { + return common.Hash{}, core.ErrTxSetsLegacyAndEIP1559Fields + } + if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) { + return common.Hash{}, core.ErrMissingGasFields + } return SubmitTransaction(ctx, s.b, tx) } diff --git a/les/benchmark.go b/les/benchmark.go index 42eeef10f..d33baf5b4 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -180,7 +180,7 @@ func (b *benchmarkTxSend) init(h *serverHandler, count int) error { for i := range b.txs { data := make([]byte, txSizeCostLimit) rand.Read(data) - tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key) + tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data, nil, nil), signer, key) if err != nil { panic(err) } diff --git a/les/handler_test.go b/les/handler_test.go index aad8d18e4..5e10cf8ee 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -538,16 +538,16 @@ 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), 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), signer, bankKey) + tx1, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil, nil, nil), signer, bankKey) test(tx1, false, light.TxStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown test(tx1, true, light.TxStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending test(tx1, true, light.TxStatus{Status: core.TxStatusPending}) // adding it again should not return an error - tx2, _ := types.SignTx(types.NewTransaction(1, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey) - tx3, _ := types.SignTx(types.NewTransaction(2, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey) + tx2, _ := types.SignTx(types.NewTransaction(1, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil, nil, nil), signer, bankKey) + tx3, _ := types.SignTx(types.NewTransaction(2, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil, nil, nil), signer, bankKey) // send transactions in the wrong order, tx3 should be queued test(tx3, true, light.TxStatus{Status: core.TxStatusQueued}) test(tx2, true, light.TxStatus{Status: core.TxStatusPending}) diff --git a/les/odr_test.go b/les/odr_test.go index 7d1087822..a1120e716 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -128,25 +128,33 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai from := statedb.GetOrNewStateObject(bankAddr) from.SetBalance(math.MaxBig256) - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false, nil, nil)} context := core.NewEVMContext(msg, header, bc, nil) vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) + var gp1559 *core.GasPool + if config.IsEIP1559(header.Number) { + gp1559 = new(core.GasPool).AddGas(math.MaxUint64) + } + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, gp1559) res = append(res, ret...) } } else { header := lc.GetHeaderByHash(bhash) state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) - msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false, nil, nil)} context := core.NewEVMContext(msg, header, lc, nil) vmenv := vm.NewEVM(context, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) + var gp1559 *core.GasPool + if config.IsEIP1559(header.Number) { + gp1559 = new(core.GasPool).AddGas(math.MaxUint64) + } + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, gp1559) if state.Error() == nil { res = append(res, ret...) } diff --git a/les/test_helper.go b/les/test_helper.go index ee3d7a32e..665380a3d 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -111,43 +111,43 @@ 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), 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), 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), 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 - tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, userKey1) + tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(0), testContractCode, nil, nil), signer, userKey1) backend.SendTransaction(ctx, tx3) testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1) // user1 deploys a event contract - tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1) + tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode, nil, nil), signer, userKey1) backend.SendTransaction(ctx, tx4) 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), 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), 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), 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/light/odr_test.go b/light/odr_test.go index debd5544c..ec0daa75f 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -194,11 +194,15 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false, nil, nil)} context := core.NewEVMContext(msg, header, chain, nil) vmenv := vm.NewEVM(context, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) + var gp1559 *core.GasPool + if config.IsEIP1559(header.Number) { + gp1559 = new(core.GasPool).AddGas(math.MaxUint64) + } + ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp, gp1559) res = append(res, ret...) if st.Error() != nil { return res, st.Error() @@ -212,17 +216,17 @@ 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), 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), 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), 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), signer, acc1Key) + tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), testContractCode, nil, nil), signer, acc1Key) testContractAddr = crypto.CreateAddress(acc1Addr, nonce) block.AddTx(tx1) block.AddTx(tx2) @@ -232,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), 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). @@ -243,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), 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.go b/light/txpool.go index 11a0e76ae..c57a54ddb 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/light/txpool_test.go b/light/txpool_test.go index 0996bd7c9..29ffe5339 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), 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/stress_clique.go b/miner/stress_clique.go index 7f5db2e52..9d5df42a9 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -117,7 +117,7 @@ func main() { panic(err) } // Create a self transaction and inject into the pool - tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index]) + tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil, nil, nil), types.HomesteadSigner{}, faucets[index]) if err != nil { panic(err) } diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 7d4a7d24f..8177f4add 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -113,7 +113,7 @@ func main() { panic(err) } // Create a self transaction and inject into the pool - tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index]) + tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil, nil, nil), types.HomesteadSigner{}, faucets[index]) if err != nil { panic(err) } diff --git a/miner/worker.go b/miner/worker.go index 183499ec3..6df55e4d0 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" @@ -86,6 +86,7 @@ type environment struct { uncles mapset.Set // uncle set tcount int // tx count in cycle gasPool *core.GasPool // available gas used to pack transactions + gp1559 *core.GasPool // available gas used to pack EIP1559 transactions header *types.Header txs []*types.Transaction @@ -454,7 +455,21 @@ func (w *worker) mainLoop() { // be automatically eliminated. if !w.isRunning() && w.current != nil { // If block is already full, abort - if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas { + legacyGasPool := w.current.gasPool + eip1559GasPool := w.current.gp1559 + // If EIP1559 is finalized we only accept 1559 transactions so if that pool is exhausted the block is full + if w.chainConfig.IsEIP1559Finalized(w.chain.CurrentBlock().Number()) && eip1559GasPool != nil && eip1559GasPool.Gas() < params.TxGas { + continue + } + // If EIP1559 has not been initialized we only accept legacy transaction so if that pool is exhausted the block is full + if !w.chainConfig.IsEIP1559(w.chain.CurrentBlock().Number()) && legacyGasPool != nil && legacyGasPool.Gas() < params.TxGas { + continue + } + // When we are between EIP1559 activation and finalization we can received transactions of both types + // and one pool could be exhausted while the other is not + // If both pools are exhausted we know the block is full but if only one is we could still accept transactions + // of the other type so we need to proceed into commitTransactions() + if legacyGasPool != nil && legacyGasPool.Gas() < params.TxGas && eip1559GasPool != nil && eip1559GasPool.Gas() < params.TxGas { continue } w.mu.RLock() @@ -483,7 +498,7 @@ func (w *worker) mainLoop() { } atomic.AddInt32(&w.newTxs, int32(len(ev.Txs))) - // System stopped + // System stopped case <-w.exitCh: return case <-w.txsSub.Err(): @@ -704,7 +719,7 @@ func (w *worker) updateSnapshot() { func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { snap := w.current.state.Snapshot() - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.gp1559, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { w.current.state.RevertToSnapshot(snap) return nil, err @@ -721,13 +736,74 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return true } - if w.current.gasPool == nil { - w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit) + // If EIP1559 is initialized then header.GasLimit is for the EIP1559 pool + // and the difference between the MaxGasEIP1559 and header.GasLimit is the limit for the legacy pool + // Once EIP1559 is finalized the header.GasLimit is the entire MaxGasEIP1559 + // so no gas will be allocated to the legacy pool + var eip1559GasLimit uint64 + var legacyGasLimit uint64 + if w.chainConfig.IsEIP1559(w.current.header.Number) { + if w.current.gasPool == nil { + legacyGasLimit = params.MaxGasEIP1559 - w.current.header.GasLimit + w.current.gasPool = new(core.GasPool).AddGas(legacyGasLimit) + } + if w.current.gp1559 == nil { + eip1559GasLimit = w.current.header.GasLimit + 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 + w.current.gasPool = new(core.GasPool).AddGas(legacyGasLimit) + } + + oneTxType := false + if w.chainConfig.IsEIP1559Finalized(w.current.header.Number) || !w.chainConfig.IsEIP1559(w.current.header.Number) { + oneTxType = true } var coalescedLogs []*types.Log for { + // Retrieve the next transaction and abort if all done + tx := txs.Peek() + if tx == nil { + break + } + // Set which gasPool to use based on the type of transaction + eip1559 := false + var gp *core.GasPool + if w.chainConfig.IsEIP1559(w.current.header.Number) && tx.GasPrice() == nil && tx.GasPremium() != nil && tx.FeeCap() != nil { + gp = w.current.gp1559 + eip1559 = true + } else if !w.chainConfig.IsEIP1559Finalized(w.current.header.Number) && tx.GasPremium() == nil && tx.FeeCap() == nil && tx.GasPrice() != nil { + gp = w.current.gasPool + } else { + log.Error("Transaction does not conform with expected format (legacy or EIP1559)") + continue + } + + // If we processing both types of transactions then we can break if both pools are exhausted + if w.current.gasPool != nil && w.current.gasPool.Gas() < params.TxGas && w.current.gp1559 != nil && w.current.gp1559.Gas() < params.TxGas { + log.Trace("Not enough gas for further transactions", "have legacy pool", w.current.gasPool, "and eip1559 pool", w.current.gp1559, "want", params.TxGas) + break + } + + // If we don't have enough gas for any further transactions of this type + if gp.Gas() < params.TxGas { + if eip1559 { + log.Trace("Not enough gas for further EIP1559 transactions", "have", gp, "want", params.TxGas) + } else { + log.Trace("Not enough gas for further legacy transactions", "have", gp, "want", params.TxGas) + } + // and this is the only type we are processing, then we're done + if oneTxType { + break + } + // Otherwise if only the current pool is exhausted we need to continue + // in case some of the subsequent transactions are for the non-exhausted pool + continue + } + // In the following three cases, we will interrupt the execution of the transaction. // (1) new head block event arrival, the interrupt signal is 1 // (2) worker start or restart, the interrupt signal is 1 @@ -737,7 +813,12 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { // Notify resubmit loop to increase resubmitting interval due to too frequent commits. if atomic.LoadInt32(interrupt) == commitInterruptResubmit { - ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit) + var ratio float64 + if eip1559 { + ratio = float64(eip1559GasLimit-gp.Gas()) / float64(eip1559GasLimit) + } else { + ratio = float64(legacyGasLimit-gp.Gas()) / float64(legacyGasLimit) + } if ratio < 0.1 { ratio = 0.1 } @@ -748,16 +829,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin } return atomic.LoadInt32(interrupt) == commitInterruptNewHead } - // If we don't have enough gas for any further transactions then we're done - if w.current.gasPool.Gas() < params.TxGas { - log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) - break - } - // Retrieve the next transaction and abort if all done - tx := txs.Peek() - if tx == nil { - break - } + // Error may be ignored here. The error has already been checked // during transaction acceptance is the transaction pool. // @@ -847,10 +919,12 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) } num := parent.Number() + gasLimit, baseFee := core.CalcGasLimitAndBaseFee(w.chainConfig, parent, w.config.GasFloor, w.config.GasCeil) header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), - GasLimit: core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil), + GasLimit: gasLimit, + BaseFee: baseFee, Extra: w.extra, Time: uint64(timestamp), } @@ -979,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 faebee1a7..bd03dfeb8 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, nil, nil), types.HomesteadSigner{}, testBankKey) - pendingTxs = append(pendingTxs, tx1) - tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, 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, nil, common.FromHex(testCode)), 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, 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 { diff --git a/mobile/ethereum.go b/mobile/ethereum.go index 59da85239..258ecfa8b 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 b9c44c25d..328f5cc94 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" @@ -199,17 +200,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) *Transaction { - return &Transaction{types.NewContractCreation(uint64(nonce), amount.bigint, uint64(gasLimit), gasPrice.bigint, common.CopyBytes(data))} +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) *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))} + 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))} + 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. @@ -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)} } diff --git a/params/config.go b/params/config.go index c90de56dc..8fbec48ee 100644 --- a/params/config.go +++ b/params/config.go @@ -54,19 +54,22 @@ var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{ var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(1150000), - DAOForkBlock: big.NewInt(1920000), - DAOForkSupport: true, - EIP150Block: big.NewInt(2463000), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(2675000), - EIP158Block: big.NewInt(2675000), - ByzantiumBlock: big.NewInt(4370000), - ConstantinopleBlock: big.NewInt(7280000), - PetersburgBlock: big.NewInt(7280000), - IstanbulBlock: big.NewInt(9069000), - Ethash: new(EthashConfig), + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(1150000), + DAOForkBlock: big.NewInt(1920000), + DAOForkSupport: true, + EIP150Block: big.NewInt(2463000), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(2675000), + EIP158Block: big.NewInt(2675000), + ByzantiumBlock: big.NewInt(4370000), + ConstantinopleBlock: big.NewInt(7280000), + PetersburgBlock: big.NewInt(7280000), + IstanbulBlock: big.NewInt(9069000), + EIP1559Block: nil, + EIP1559FinalizedBlock: nil, + EWASMBlock: nil, + Ethash: new(EthashConfig), } // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. @@ -92,19 +95,22 @@ var ( // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. TestnetChainConfig = &ChainConfig{ - ChainID: big.NewInt(3), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), - EIP155Block: big.NewInt(10), - EIP158Block: big.NewInt(10), - ByzantiumBlock: big.NewInt(1700000), - ConstantinopleBlock: big.NewInt(4230000), - PetersburgBlock: big.NewInt(4939394), - IstanbulBlock: big.NewInt(6485846), - Ethash: new(EthashConfig), + ChainID: big.NewInt(3), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), + EIP155Block: big.NewInt(10), + EIP158Block: big.NewInt(10), + ByzantiumBlock: big.NewInt(1700000), + ConstantinopleBlock: big.NewInt(4230000), + PetersburgBlock: big.NewInt(4939394), + IstanbulBlock: big.NewInt(6485846), + EIP1559Block: nil, + EIP1559FinalizedBlock: nil, + EWASMBlock: nil, + Ethash: new(EthashConfig), } // TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. @@ -130,18 +136,21 @@ var ( // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. RinkebyChainConfig = &ChainConfig{ - ChainID: big.NewInt(4), - HomesteadBlock: big.NewInt(1), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(2), - EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), - EIP155Block: big.NewInt(3), - EIP158Block: big.NewInt(3), - ByzantiumBlock: big.NewInt(1035301), - ConstantinopleBlock: big.NewInt(3660663), - PetersburgBlock: big.NewInt(4321234), - IstanbulBlock: big.NewInt(5435345), + ChainID: big.NewInt(4), + HomesteadBlock: big.NewInt(1), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(2), + EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), + EIP155Block: big.NewInt(3), + EIP158Block: big.NewInt(3), + ByzantiumBlock: big.NewInt(1035301), + ConstantinopleBlock: big.NewInt(3660663), + PetersburgBlock: big.NewInt(4321234), + IstanbulBlock: big.NewInt(5435345), + EIP1559Block: nil, + EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -170,17 +179,20 @@ var ( // GoerliChainConfig contains the chain parameters to run a node on the Görli test network. GoerliChainConfig = &ChainConfig{ - ChainID: big.NewInt(5), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(1561651), + ChainID: big.NewInt(5), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(1561651), + EIP1559Block: nil, + EIP1559FinalizedBlock: nil, + EWASMBlock: nil, Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -213,17 +225,25 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), 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, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), 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, nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), 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, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), 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, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &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, new(EthashConfig), nil} - TestRules = TestChainConfig.Rules(new(big.Int)) + TestChainConfig = &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, nil, nil, new(EthashConfig), nil} + + 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), 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)) ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and @@ -288,11 +308,13 @@ type ChainConfig struct { EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF block - ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium) - 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) + ByzantiumBlock *big.Int `json:"byzantiumBlock,omitempty"` // Byzantium switch block (nil = no fork, 0 = already on byzantium) + 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) + 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"` @@ -329,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 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, @@ -341,6 +363,9 @@ func (c *ChainConfig) String() string { c.ConstantinopleBlock, c.PetersburgBlock, c.IstanbulBlock, + c.EIP1559Block, + c.EIP1559FinalizedBlock, + c.EWASMBlock, engine, ) } @@ -397,6 +422,16 @@ func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) } +// IsEIP1559 returns whether num represents a block number after the EIP1559 fork +func (c *ChainConfig) IsEIP1559(num *big.Int) bool { + return isForked(c.EIP1559Block, num) +} + +// IsEIP1559Finalized returns whether num represents a block number after the EIP1559 finalization fork +func (c *ChainConfig) IsEIP1559Finalized(num *big.Int) bool { + return isForked(c.EIP1559FinalizedBlock, num) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { @@ -432,6 +467,9 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {"constantinopleBlock", c.ConstantinopleBlock}, {"petersburgBlock", c.PetersburgBlock}, {"istanbulBlock", c.IstanbulBlock}, + {"eip1559Block", c.EIP1559Block}, + {"eip1559FinalizedBlock", c.EIP1559FinalizedBlock}, + {"ewasmBlock", c.EWASMBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -485,8 +523,14 @@ 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.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 newCompatError("EWASM fork block", c.EWASMBlock, newcfg.EWASMBlock) } return nil } @@ -555,6 +599,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool + IsEIP1559, IsEIP1559Finalized, IsEWASM bool } // Rules ensures c's ChainID is not nil. @@ -564,14 +609,17 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { chainID = new(big.Int) } return Rules{ - ChainID: new(big.Int).Set(chainID), - IsHomestead: c.IsHomestead(num), - IsEIP150: c.IsEIP150(num), - IsEIP155: c.IsEIP155(num), - IsEIP158: c.IsEIP158(num), - IsByzantium: c.IsByzantium(num), - IsConstantinople: c.IsConstantinople(num), - IsPetersburg: c.IsPetersburg(num), - IsIstanbul: c.IsIstanbul(num), + ChainID: new(big.Int).Set(chainID), + IsHomestead: c.IsHomestead(num), + IsEIP150: c.IsEIP150(num), + IsEIP155: c.IsEIP155(num), + IsEIP158: c.IsEIP158(num), + IsByzantium: c.IsByzantium(num), + IsConstantinople: c.IsConstantinople(num), + IsPetersburg: c.IsPetersburg(num), + IsIstanbul: c.IsIstanbul(num), + IsEIP1559: c.IsEIP1559(num), + IsEIP1559Finalized: c.IsEIP1559Finalized(num), + IsEWASM: c.IsEWASM(num), } } diff --git a/params/protocol_params.go b/params/protocol_params.go index 11b858a61..69fb1451a 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -129,6 +129,16 @@ const ( Bn256PairingBaseGasIstanbul uint64 = 45000 // Base price for an elliptic curve pairing check Bn256PairingPerPointGasByzantium uint64 = 80000 // Byzantium per-point price for an elliptic curve pairing check Bn256PairingPerPointGasIstanbul uint64 = 34000 // Per-point price for an elliptic curve pairing check + + EIP1559InitialBaseFee uint64 = 1000000000 // Wei used as the initial BaseFee + EIP1559ForkBlockNumber uint64 = 100000000 // TBD + EIP1559ForkFinalizedBlockNumber = EIP1559ForkBlockNumber + (MaxGasEIP1559 / (10 * SlackCoefficient)) + BaseFeeMaxChangeDenominator uint64 = 8 + SlackCoefficient uint64 = 2 + TargetGasUsed uint64 = 8000000 + MaxGasEIP1559 = SlackCoefficient * TargetGasUsed + EIP1559DecayRange = EIP1559ForkFinalizedBlockNumber - EIP1559ForkBlockNumber + EIP1559GasIncrementAmount = (MaxGasEIP1559 / 2) / EIP1559DecayRange // We need to shift (MaxGasEIP1559 / 2) gas from the legacy pool into the EIP1559 pool over the EIP1559DecayRange ) var ( diff --git a/rlp/decode.go b/rlp/decode.go index 524395915..b5c9fbf16 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -42,10 +42,10 @@ var ( ErrElemTooLarge = errors.New("rlp: element is larger than containing list") ErrValueTooLarge = errors.New("rlp: value size exceeds available input length") ErrMoreThanOneValue = errors.New("rlp: input contains more than one value") + ErrNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL") // internal errors errNotInList = errors.New("rlp: call of ListEnd outside of any list") - errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL") errUintOverflow = errors.New("rlp: uint overflow") errNoPointer = errors.New("rlp: interface given to Decode must be a pointer") errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil") @@ -129,7 +129,7 @@ func wrapStreamError(err error, typ reflect.Type) error { return &decodeError{msg: "expected input string or byte", typ: typ} case errUintOverflow: return &decodeError{msg: "input string too long", typ: typ} - case errNotAtEOL: + case ErrNotAtEOL: return &decodeError{msg: "input list has too many elements", typ: typ} } return err @@ -730,7 +730,7 @@ func (s *Stream) ListEnd() error { } tos := s.stack[len(s.stack)-1] if tos.pos != tos.size { - return errNotAtEOL + return ErrNotAtEOL } s.stack = s.stack[:len(s.stack)-1] // pop if len(s.stack) > 0 { @@ -766,6 +766,7 @@ func (s *Stream) Decode(val interface{}) error { // add decode target type to error so context has more meaning decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) } + return err } diff --git a/signer/core/types.go b/signer/core/types.go index f147f06cd..10321d6ae 100644 --- a/signer/core/types.go +++ b/signer/core/types.go @@ -94,7 +94,7 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { input = *args.Input } if args.To == nil { - return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input, nil, nil) } - return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) + return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input, nil, nil) } diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index c030ed47c..159e16fda 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -458,7 +458,7 @@ func dummySigned(value *big.Int) *types.Transaction { gas := uint64(21000) gasPrice := big.NewInt(2000000) data := make([]byte, 0) - return types.NewTransaction(3, to, value, gas, gasPrice, data) + return types.NewTransaction(3, to, value, gas, gasPrice, data, nil, nil) } func TestLimitWindow(t *testing.T) { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 59ebcb6e1..21a16ff3d 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -181,10 +181,17 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, statedb, config, vmconfig) - gaspool := new(core.GasPool) - gaspool.AddGas(block.GasLimit()) + var gp1559 *core.GasPool + var gaspool *core.GasPool + if config.IsEIP1559(block.Number()) { + gaspool = new(core.GasPool).AddGas(params.MaxGasEIP1559 - block.GasLimit()) + gp1559 = new(core.GasPool).AddGas(block.GasLimit()) + } else { + gaspool = new(core.GasPool).AddGas(block.GasLimit()) + } + snapshot := statedb.Snapshot() - if _, _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { + if _, _, _, err := core.ApplyMessage(evm, msg, gaspool, gp1559); err != nil { statedb.RevertToSnapshot(snapshot) } // Commit block @@ -279,7 +286,7 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { return nil, fmt.Errorf("invalid tx data %q", dataHex) } - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true) + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true, nil, nil) return msg, nil }