core, internal/ethapi: reject tx value and gas price above uint256 max

Raw RLP transactions can decode values larger than uint256, so reject them during txpool validation before they fall through to state-dependent checks.

Also return ErrUint256Overflow from state transition and map it to an invalid params RPC error so raw transaction submission reports a consistent parameter validation failure.

Add focused regression tests for txpool validation and eth_sendRawTransaction error handling.
This commit is contained in:
Daniel Liu 2026-04-08 17:28:09 +08:00
parent b1baab4427
commit bbfa8d9255
5 changed files with 103 additions and 2 deletions

View file

@ -481,7 +481,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
return nil, types.ErrUint256Overflow
}
if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())

View file

@ -97,9 +97,19 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur for transactions created using the RPC.
if tx.Value().Sign() < 0 {
val := tx.Value()
if val.Sign() < 0 {
return ErrNegativeValue
}
if val.BitLen() > 256 {
return types.ErrUint256Overflow
}
// For legacy transactions, ensure gasPrice does not exceed 256 bits
if tx.Type() == types.LegacyTxType {
if tx.GasPrice().BitLen() > 256 {
return types.ErrUint256Overflow
}
}
// Ensure the transaction doesn't exceed the current block limit gas
if head.GasLimit < tx.Gas() {
return ErrGasLimit

View file

@ -96,6 +96,44 @@ func TestValidateTransactionEIP2681(t *testing.T) {
}
}
func TestValidateTransactionValueOverflow(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal(err)
}
head := &types.Header{
Number: big.NewInt(1),
GasLimit: 5000000,
Time: 1,
Difficulty: big.NewInt(1),
}
signer := types.LatestSigner(params.TestChainConfig)
opts := &ValidationOptions{
Config: params.TestChainConfig,
Accept: 0xFF,
MaxSize: 32 * 1024,
MaxBlobCount: 6,
MinTip: big.NewInt(0),
}
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
overflowValue := new(big.Int).Lsh(big.NewInt(1), 256)
tx := types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &to,
Value: overflowValue,
Gas: 21000,
GasPrice: big.NewInt(1),
})
tx, err = types.SignTx(tx, types.HomesteadSigner{}, key)
if err != nil {
t.Fatal(err)
}
err = ValidateTransaction(tx, head, signer, opts)
if !errors.Is(err, types.ErrUint256Overflow) {
t.Fatalf("expected %v, got %v", types.ErrUint256Overflow, err)
}
}
// createTestTransaction creates a basic transaction for testing
func createTestTransaction(key *ecdsa.PrivateKey, nonce uint64) *types.Transaction {
to := common.HexToAddress("0x0000000000000000000000000000000000000001")

View file

@ -3968,6 +3968,11 @@ type configTimeBackend struct {
time uint64
}
type overflowRejectingBackend struct {
*backendMock
lastTx *types.Transaction
}
func (b configTimeBackend) ChainConfig() *params.ChainConfig {
return b.genesis.Config
}
@ -3998,6 +4003,14 @@ func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration {
func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second }
func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute }
func (b *overflowRejectingBackend) SendTx(ctx context.Context, tx *types.Transaction) error {
b.lastTx = tx
if tx.Value().BitLen() > 256 {
return types.ErrUint256Overflow
}
return nil
}
func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) {
t.Helper()
@ -4089,6 +4102,43 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) {
}
}
func TestSendRawTransactionRejectsValueOverflow(t *testing.T) {
t.Parallel()
key, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
require.NoError(t, err)
backend := &overflowRejectingBackend{backendMock: newBackendMock()}
api := NewTransactionAPI(backend, nil)
to := common.Address{0x42}
overflowValue := new(big.Int).Lsh(big.NewInt(1), 256)
tx, err := types.SignNewTx(key, types.LatestSigner(backend.ChainConfig()), &types.LegacyTx{
Nonce: 0,
GasPrice: big.NewInt(2),
Gas: 21000,
To: &to,
Value: overflowValue,
})
require.NoError(t, err)
raw, err := tx.MarshalBinary()
require.NoError(t, err)
_, err = api.SendRawTransaction(context.Background(), raw)
require.ErrorIs(t, err, types.ErrUint256Overflow)
require.NotNil(t, backend.lastTx)
require.Equal(t, overflowValue, backend.lastTx.Value())
}
func TestTxValidationErrorUint256Overflow(t *testing.T) {
t.Parallel()
err := txValidationError(types.ErrUint256Overflow)
require.Equal(t, errCodeInvalidParams, err.ErrorCode())
require.ErrorContains(t, err, types.ErrUint256Overflow.Error())
}
func TestGetStorageValues(t *testing.T) {
t.Parallel()

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)
@ -131,6 +132,8 @@ func txValidationError(err error) *invalidTxError {
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
case errors.Is(err, core.ErrTipVeryHigh):
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
case errors.Is(err, types.ErrUint256Overflow):
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
case errors.Is(err, core.ErrTipAboveFeeCap):
return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams}
case errors.Is(err, core.ErrFeeCapTooLow):