From f32feeb260054d7c90c1efd57dfec6b717717e18 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 11 Nov 2021 15:00:58 +0100 Subject: [PATCH] core/vm: implement EIP-2681: Limit account nonce to 2^64-1 (#23853) This retroactively implements requirements or EIP-2681 for the account nonce upper limit. --- cmd/evm/internal/t8ntool/transaction.go | 2 ++ core/error.go | 4 +++ core/state_processor_test.go | 39 ++++++++++++++++--------- core/state_transition.go | 3 ++ core/vm/errors.go | 1 + core/vm/evm.go | 3 ++ 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index bc9bc42edc17..576d1db84b9f 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -154,6 +154,8 @@ func Transaction(ctx *cli.Context) error { } // Validate <256bit fields switch { + case tx.Nonce()+1 < tx.Nonce(): + r.Error = errors.New("nonce exceeds 2^64-1") case tx.Value().BitLen() > 256: r.Error = errors.New("value exceeds 256 bits") case tx.GasPrice().BitLen() > 256: diff --git a/core/error.go b/core/error.go index 594f3a26e559..51ebefc137bc 100644 --- a/core/error.go +++ b/core/error.go @@ -51,6 +51,10 @@ var ( // next one expected based on the local chain. ErrNonceTooHigh = errors.New("nonce too high") + // ErrNonceMax is returned if the nonce of a transaction sender account has + // maximum allowed value and would become invalid if incremented. + ErrNonceMax = errors.New("nonce has max value") + // ErrGasLimitReached is returned by the gas pool if the amount of gas required // by a transaction is higher than what's left in the block. ErrGasLimitReached = errors.New("gas limit reached") diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 13a9eb810df6..aa8e4bebf9d4 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -17,10 +17,12 @@ package core import ( + "crypto/ecdsa" "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc" @@ -54,11 +56,12 @@ func TestStateProcessorErrors(t *testing.T) { LondonBlock: big.NewInt(0), Ethash: new(params.EthashConfig), } - signer = types.LatestSigner(config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + signer = types.LatestSigner(config) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("0202020202020202020202020202020202020202020202020202002020202020") ) - var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { - tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey) + var makeTx = func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key) return tx } var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { @@ -69,7 +72,7 @@ func TestStateProcessorErrors(t *testing.T) { Gas: gasLimit, To: &to, Value: big.NewInt(0), - }), signer, testKey) + }), signer, key1) return tx } { // Tests against a 'recent' chain definition @@ -82,6 +85,10 @@ func TestStateProcessorErrors(t *testing.T) { Balance: big.NewInt(1000000000000000000), // 1 ether Nonce: 0, }, + common.HexToAddress("0xfd0810DD14796680f72adf1a371963d0745BCc64"): GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: math.MaxUint64, + }, }, } genesis = gspec.MustCommit(db) @@ -97,32 +104,38 @@ func TestStateProcessorErrors(t *testing.T) { }{ { // ErrNonceTooLow txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), }, want: "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", }, { // ErrNonceTooHigh txs: []*types.Transaction{ - makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(key1, 100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), }, want: "could not apply tx 0 [0xdebad714ca7f363bd0d8121c4518ad48fa469ca81b0a081be3d10c17460f751b]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", }, + { // ErrNonceMax + txs: []*types.Transaction{ + makeTx(key2, math.MaxUint64, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0x84ea18d60eb2bb3b040e3add0eb72f757727122cc257dd858c67cb6591a85986]: nonce has max value: address 0xfd0810DD14796680f72adf1a371963d0745BCc64, nonce: 18446744073709551615", + }, { // ErrGasLimitReached txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), 21000000, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), 21000000, big.NewInt(875000000), nil), }, want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", }, { // ErrInsufficientFundsForTransfer txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(1000000000000000000), params.TxGas, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(1000000000000000000), params.TxGas, big.NewInt(875000000), nil), }, want: "could not apply tx 0 [0x98c796b470f7fcab40aaef5c965a602b0238e1034cce6fb73823042dd0638d74]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 1000018375000000000", }, { // ErrInsufficientFunds txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil), }, want: "could not apply tx 0 [0x4a69690c4b0cd85e64d0d9ea06302455b01e10a83db964d60281739752003440]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 18900000000000000000000", }, @@ -132,13 +145,13 @@ func TestStateProcessorErrors(t *testing.T) { // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment { // ErrIntrinsicGas txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), nil), }, want: "could not apply tx 0 [0xcf3b049a0b516cb4f9274b3e2a264359e2ba53b2fb64b7bda2c634d5c9d01fca]: intrinsic gas too low: have 20000, want 21000", }, { // ErrGasLimitReached txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas*1000, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas*1000, big.NewInt(875000000), nil), }, want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", }, diff --git a/core/state_transition.go b/core/state_transition.go index 6a09f6adc441..135a9c6dbe85 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -222,6 +222,9 @@ func (st *StateTransition) preCheck() error { } else if stNonce > msgNonce { return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce+1 < stNonce { + return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, + st.msg.From().Hex(), stNonce) } // Make sure the sender is an EOA if codeHash := st.state.GetCodeHash(st.msg.From()); codeHash != emptyCodeHash && codeHash != (common.Hash{}) { diff --git a/core/vm/errors.go b/core/vm/errors.go index c7cfeae53ce3..565eecdd7cff 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -35,6 +35,7 @@ var ( ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index 07e96191588a..618bbcf176cb 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -424,6 +424,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrInsufficientBalance } nonce := evm.StateDB.GetNonce(caller.Address()) + if nonce+1 < nonce { + return nil, common.Address{}, gas, ErrNonceUintOverflow + } evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back