Skip to content

Commit bacc150

Browse files
authored
core/txpool: add eip2681 check for incoming transactions (#32726)
1 parent ad484fc commit bacc150

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

core/txpool/validation.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
116116
if _, err := types.Sender(signer, tx); err != nil {
117117
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
118118
}
119+
// Limit nonce to 2^64-1 per EIP-2681
120+
if tx.Nonce()+1 < tx.Nonce() {
121+
return core.ErrNonceMax
122+
}
119123
// Ensure the transaction has more gas than the bare minimum needed to cover
120124
// the transaction metadata
121125
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)

core/txpool/validation_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package txpool
18+
19+
import (
20+
"crypto/ecdsa"
21+
"errors"
22+
"math"
23+
"math/big"
24+
"testing"
25+
26+
"github.com/ethereum/go-ethereum/common"
27+
"github.com/ethereum/go-ethereum/core"
28+
"github.com/ethereum/go-ethereum/core/types"
29+
"github.com/ethereum/go-ethereum/crypto"
30+
"github.com/ethereum/go-ethereum/params"
31+
)
32+
33+
func TestValidateTransactionEIP2681(t *testing.T) {
34+
key, err := crypto.GenerateKey()
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
head := &types.Header{
40+
Number: big.NewInt(1),
41+
GasLimit: 5000000,
42+
Time: 1,
43+
Difficulty: big.NewInt(1),
44+
}
45+
46+
signer := types.LatestSigner(params.TestChainConfig)
47+
48+
// Create validation options
49+
opts := &ValidationOptions{
50+
Config: params.TestChainConfig,
51+
Accept: 0xFF, // Accept all transaction types
52+
MaxSize: 32 * 1024,
53+
MaxBlobCount: 6,
54+
MinTip: big.NewInt(0),
55+
}
56+
57+
tests := []struct {
58+
name string
59+
nonce uint64
60+
wantErr error
61+
}{
62+
{
63+
name: "normal nonce",
64+
nonce: 42,
65+
wantErr: nil,
66+
},
67+
{
68+
name: "max allowed nonce (2^64-2)",
69+
nonce: math.MaxUint64 - 1,
70+
wantErr: nil,
71+
},
72+
{
73+
name: "EIP-2681 nonce overflow (2^64-1)",
74+
nonce: math.MaxUint64,
75+
wantErr: core.ErrNonceMax,
76+
},
77+
}
78+
79+
for _, tt := range tests {
80+
t.Run(tt.name, func(t *testing.T) {
81+
tx := createTestTransaction(key, tt.nonce)
82+
err := ValidateTransaction(tx, head, signer, opts)
83+
84+
if tt.wantErr == nil {
85+
if err != nil {
86+
t.Errorf("ValidateTransaction() error = %v, wantErr nil", err)
87+
}
88+
} else {
89+
if err == nil {
90+
t.Errorf("ValidateTransaction() error = nil, wantErr %v", tt.wantErr)
91+
} else if !errors.Is(err, tt.wantErr) {
92+
t.Errorf("ValidateTransaction() error = %v, wantErr %v", err, tt.wantErr)
93+
}
94+
}
95+
})
96+
}
97+
}
98+
99+
// createTestTransaction creates a basic transaction for testing
100+
func createTestTransaction(key *ecdsa.PrivateKey, nonce uint64) *types.Transaction {
101+
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
102+
103+
txdata := &types.LegacyTx{
104+
Nonce: nonce,
105+
To: &to,
106+
Value: big.NewInt(1000),
107+
Gas: 21000,
108+
GasPrice: big.NewInt(1),
109+
Data: nil,
110+
}
111+
112+
tx := types.NewTx(txdata)
113+
signedTx, _ := types.SignTx(tx, types.HomesteadSigner{}, key)
114+
return signedTx
115+
}

eth/api_backend_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"crypto/ecdsa"
2222
"errors"
23+
"math"
2324
"math/big"
2425
"testing"
2526
"time"
@@ -130,6 +131,27 @@ func TestSendTx(t *testing.T) {
130131
testSendTx(t, true)
131132
}
132133

134+
func TestSendTxEIP2681(t *testing.T) {
135+
b := initBackend(false)
136+
137+
// Test EIP-2681: nonce overflow should be rejected
138+
tx := makeTx(uint64(math.MaxUint64), nil, nil, key) // max uint64 nonce
139+
err := b.SendTx(context.Background(), tx)
140+
if err == nil {
141+
t.Fatal("Expected EIP-2681 nonce overflow error, but transaction was accepted")
142+
}
143+
if !errors.Is(err, core.ErrNonceMax) {
144+
t.Errorf("Expected core.ErrNonceMax, got: %v", err)
145+
}
146+
147+
// Test normal case: should succeed
148+
normalTx := makeTx(0, nil, nil, key)
149+
err = b.SendTx(context.Background(), normalTx)
150+
if err != nil {
151+
t.Errorf("Normal transaction should succeed, got error: %v", err)
152+
}
153+
}
154+
133155
func testSendTx(t *testing.T, withLocal bool) {
134156
b := initBackend(withLocal)
135157

0 commit comments

Comments
 (0)