diff --git a/core/state_transition.go b/core/state_transition.go index bd7e5daeff..6286256559 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -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()) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 13b1bfa312..aa15ee869e 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -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 diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index 3945b548c1..b261c2d95d 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -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") diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index b010eeaa08..2ed2042667 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -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() diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index cc79af6f3c..cd9be8bd47 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -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):