diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 7151ecf8f6..7928882ac1 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -663,6 +663,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if err != nil { return ErrInvalidSender } + // Limit nonce to 2^64-1 per EIP-2681 + if tx.Nonce()+1 < tx.Nonce() { + return core.ErrNonceMax + } // Drop non-local transactions under our own minimal accepted gas price or tip if !local { isUnderpriced := false diff --git a/core/txpool/txpool_test.go b/core/txpool/txpool_test.go index 5ddd7857e9..c1b4dff50f 100644 --- a/core/txpool/txpool_test.go +++ b/core/txpool/txpool_test.go @@ -18,7 +18,9 @@ package txpool import ( "crypto/ecdsa" + "errors" "fmt" + "math" "math/big" "math/rand" "os" @@ -403,6 +405,53 @@ func TestNegativeValue(t *testing.T) { } } +// TestValidateTransactionEIP2681 tests that the pool correctly validates transactions +// according to EIP-2681, which limits the nonce to a maximum value of 2^64 - 1. +func TestValidateTransactionEIP2681(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Stop() + + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, addr, big.NewInt(2e18)) + + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + + tests := []struct { + name string + nonce uint64 + value *big.Int + gas uint64 + gasPrice *big.Int + wantErr error + }{ + {"nonce 0", 0, big.NewInt(1e18), 21000, big.NewInt(2e10), nil}, + {"nonce 1", 1, big.NewInt(1e18), 21000, big.NewInt(2e10), nil}, + {"EIP-2681 overflow", math.MaxUint64, big.NewInt(1e18), 21000, big.NewInt(2e10), core.ErrNonceMax}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tx := types.NewTx(&types.LegacyTx{ + Nonce: tt.nonce, + To: &to, + Value: tt.value, + Gas: tt.gas, + GasPrice: tt.gasPrice, + }) + signedTx, _ := types.SignTx(tx, types.HomesteadSigner{}, key) + err := pool.validateTx(signedTx, true) + + if tt.wantErr == nil && err != nil { + t.Errorf("expected nil, got %v", err) + } else if tt.wantErr != nil && !errors.Is(err, tt.wantErr) { + t.Errorf("expected %v, got %v", tt.wantErr, err) + } + }) + } +} + func TestTipAboveFeeCap(t *testing.T) { t.Parallel()