diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 611cf5d663b9..cc140023d39f 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -19,6 +19,7 @@ package core import ( "crypto/ecdsa" "encoding/binary" + "errors" "fmt" "math" "math/big" @@ -598,6 +599,63 @@ func TestApplyTransactionWithEVMStateChangeHooks(t *testing.T) { } } +func TestApplyTransactionWithEVMRejectsValueOverflow(t *testing.T) { + t.Parallel() + + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + } + signer := types.LatestSigner(config) + key, err := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + if err != nil { + t.Fatalf("Failed to create key: %v", err) + } + sender := crypto.PubkeyToAddress(key.PublicKey) + recipient := common.HexToAddress("0x1234567890123456789012345678901234567890") + tooBigValue := new(big.Int).Lsh(big.NewInt(1), 256) + hugeBalance := new(big.Int).Lsh(big.NewInt(1), 300) + + db := rawdb.NewMemoryDatabase() + gspec := &Genesis{ + Config: config, + Alloc: types.GenesisAlloc{ + sender: { + Balance: hugeBalance, + }, + }, + } + genesis := gspec.MustCommit(db) + blockchain, _ := NewBlockChain(db, nil, gspec, ethash.NewFaker(), vm.Config{}) + defer blockchain.Stop() + + statedb, err := blockchain.State() + if err != nil { + t.Fatalf("Failed to get state: %v", err) + } + tx := types.NewTransaction(0, recipient, tooBigValue, 21000, big.NewInt(1), nil) + signedTx, err := types.SignTx(tx, signer, key) + if err != nil { + t.Fatalf("Failed to sign tx: %v", err) + } + msg, err := TransactionToMessage(signedTx, signer, nil, big.NewInt(1), nil) + if err != nil { + t.Fatalf("Failed to build message: %v", err) + } + vmContext := NewEVMBlockContext(blockchain.CurrentBlock(), blockchain, nil) + evmenv := vm.NewEVM(vmContext, statedb, nil, blockchain.Config(), vm.Config{}) + gasPool := new(GasPool).AddGas(1000000) + var usedGas uint64 + _, _, _, err = ApplyTransactionWithEVM(msg, gasPool, statedb, big.NewInt(1), genesis.Hash(), signedTx, &usedGas, evmenv, nil, common.Address{}) + if !errors.Is(err, types.ErrUint256Overflow) { + t.Fatalf("expected %v, got %v", types.ErrUint256Overflow, err) + } +} + func TestProcessParentBlockHash(t *testing.T) { var ( chainConfig = params.MergedTestChainConfig diff --git a/core/state_transition.go b/core/state_transition.go index 98467beb58a3..9aa6a5c5a0e4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -433,7 +433,7 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult, // Check clause 6 value, overflow := uint256.FromBig(msg.Value) if overflow { - return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) + return nil, fmt.Errorf("%w: address %v", types.ErrUint256Overflow, msg.From.Hex()) } 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/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 03e490bc646c..93867a09e442 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -733,6 +733,19 @@ func TestNegativeValue(t *testing.T) { } } +func TestValueOverflow(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + tooBigValue := new(big.Int).Lsh(big.NewInt(1), 256) + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, tooBigValue, 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) + if err := pool.ValidateTxBasics(tx); !errors.Is(err, types.ErrUint256Overflow) { + t.Error("expected", types.ErrUint256Overflow, "got", err) + } +} + // 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) { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index c49153e3a7fb..cb214b70c676 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -77,9 +77,13 @@ 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 + } // Ensure the transaction doesn't exceed the current block limit gas if head.GasLimit < tx.Gas() { return ErrGasLimit diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 56188054da8d..31b29470c5a4 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -3180,6 +3180,9 @@ type txPoolBackendMock struct { func (b *sendTxBackendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { b.lastTx = signedTx + if signedTx.Value().BitLen() > 256 { + return types.ErrUint256Overflow + } if b.sendErr != nil { return b.sendErr } @@ -3444,6 +3447,43 @@ func TestSendRawTransactionFeeCapExceeded(t *testing.T) { require.ErrorContains(t, err, "exceeds the configured cap") } +func TestSendRawTransactionRejectsValueOverflow(t *testing.T) { + t.Parallel() + + key, err := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + require.NoError(t, err) + + backend := &sendTxBackendMock{backendMock: newBackendMock()} + api := NewTransactionAPI(backend, nil) + + to := common.Address{0x56} + overflowValue := new(big.Int).Lsh(big.NewInt(1), 256) + tx, err := types.SignNewTx(key, types.LatestSigner(backend.ChainConfig()), &types.LegacyTx{ + Nonce: 11, + 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 TestGetTransactionByHashBasic(t *testing.T) { t.Parallel() diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index 3234bc6c65ad..321c8cb6d1de 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -23,6 +23,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/accounts/abi" "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" ) @@ -107,6 +108,8 @@ func txValidationError(err error) *invalidTxError { return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrFeeCapTooLow): 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.ErrInsufficientFunds): return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds} case errors.Is(err, core.ErrIntrinsicGas):