diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 688bb337c8..180e10ac1b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1232,7 +1232,7 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts mcOpts, blockNrOrH if len(opts.BlockStateCalls) == 0 { return nil, &invalidParamsError{message: "empty input"} } else if len(opts.BlockStateCalls) > maxMulticallBlocks { - return nil, &invalidParamsError{message: "too many blocks"} + return nil, &clientLimitExceededError{message: "too many blocks"} } if blockNrOrHash == nil { n := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index da4ca698e8..975a41e908 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1092,10 +1092,6 @@ func TestMulticallV1(t *testing.T) { From: &randomAccounts[1].addr, To: &randomAccounts[2].addr, Value: (*hexutil.Big)(big.NewInt(1000)), - }, { - From: &randomAccounts[3].addr, - To: &randomAccounts[2].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), }, }, }}, @@ -1125,12 +1121,45 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x5208", Logs: []log{}, Status: "0x1", - }, { + }}, + }}, + }, { + // insufficient funds + name: "insufficient-funds", + tag: latest, + blocks: []mcBlock{{ + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }}, + }}, + want: nil, + expectErr: &invalidTxError{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4712388)", randomAccounts[0].addr.String()), Code: errCodeInsufficientFunds}, + }, { + // EVM error + name: "evm-error", + tag: latest, + blocks: []mcBlock{{ + StateOverrides: &StateOverride{ + randomAccounts[2].addr: OverrideAccount{Code: hex2Bytes("f3")}, + }, + Calls: []TransactionArgs{{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + }}, + }}, + want: []blockRes{{ + Number: "0xb", + GasLimit: "0x47e7c4", + GasUsed: "0x47e7c4", + FeeRecipient: coinbase, + Calls: []callRes{{ ReturnValue: "0x", - GasUsed: "0x0", + Error: callErr{Message: "stack underflow (0 <=> 2)", Code: errCodeVMError}, + GasUsed: "0x47e7c4", Logs: []log{}, Status: "0x0", - Error: callErr{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), Code: errCodeInsufficientFunds}, }}, }}, }, { @@ -1219,7 +1248,7 @@ func TestMulticallV1(t *testing.T) { }}, }}, want: []blockRes{}, - expectErr: errors.New("block numbers must be in order"), + expectErr: &invalidBlockNumberError{message: fmt.Sprintf("block numbers must be in order: 11 <= 12")}, }, // Test on solidity storage example. Set value in one call, read in next. { @@ -1558,19 +1587,8 @@ func TestMulticallV1(t *testing.T) { }}, }}, validation: &validation, - want: []blockRes{{ - Number: "0xb", - GasLimit: "0x47e7c4", - GasUsed: "0x0", - FeeRecipient: coinbase, - Calls: []callRes{{ - ReturnValue: "0x", - GasUsed: "0x0", - Logs: []log{}, - Status: "0x0", - Error: callErr{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, - }}, - }}, + want: nil, + expectErr: &invalidTxError{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }, // Clear storage. { diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index fae01418d2..9a88aa8a89 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -27,43 +27,60 @@ type callError struct { Code int `json:"code"` } +type invalidTxError struct { + Message string `json:"message"` + Code int `json:"code"` +} + +func (e *invalidTxError) Error() string { return e.Message } +func (e *invalidTxError) ErrorCode() int { return e.Code } + const ( - errCodeNonceTooHigh = -38011 - errCodeNonceTooLow = -38010 - errCodeInsufficientFunds = -38014 - errCodeIntrinsicGas = -38013 - errCodeInternalError = -32603 - errCodeInvalidParams = -32602 + errCodeNonceTooHigh = -38011 + errCodeNonceTooLow = -38010 + errCodeIntrinsicGas = -38013 + errCodeInsufficientFunds = -38014 + errCodeBlockGasLimitReached = -38015 + errCodeBlockNumberInvalid = -38020 + errCodeBlockTimestampInvalid = -38021 + errCodeSenderIsNotEOA = -38024 + errCodeMaxInitCodeSizeExceeded = -38025 + errCodeClientLimitExceeded = -38026 + errCodeInternalError = -32603 + errCodeInvalidParams = -32602 + errCodeReverted = -32000 + errCodeVMError = -32015 ) -func callErrorFromError(err error) *callError { +func txValidationError(err error) *invalidTxError { if err == nil { return nil } switch { case errors.Is(err, core.ErrNonceTooHigh): - return &callError{Message: err.Error(), Code: errCodeNonceTooHigh} + return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooHigh} case errors.Is(err, core.ErrNonceTooLow): - return &callError{Message: err.Error(), Code: errCodeNonceTooLow} + return &invalidTxError{Message: err.Error(), Code: errCodeNonceTooLow} case errors.Is(err, core.ErrSenderNoEOA): - // TODO + return &invalidTxError{Message: err.Error(), Code: errCodeSenderIsNotEOA} case errors.Is(err, core.ErrFeeCapVeryHigh): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrTipVeryHigh): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrTipAboveFeeCap): - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrFeeCapTooLow): - // TODO - return &callError{Message: err.Error(), Code: errCodeInvalidParams} + return &invalidTxError{Message: err.Error(), Code: errCodeInvalidParams} case errors.Is(err, core.ErrInsufficientFunds): - return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds} case errors.Is(err, core.ErrIntrinsicGas): - return &callError{Message: err.Error(), Code: errCodeIntrinsicGas} + return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas} case errors.Is(err, core.ErrInsufficientFundsForTransfer): - return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds} + case errors.Is(err, core.ErrMaxInitCodeSizeExceeded): + return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded} } - return &callError{ + return &invalidTxError{ Message: err.Error(), Code: errCodeInternalError, } @@ -73,3 +90,23 @@ type invalidParamsError struct{ message string } func (e *invalidParamsError) Error() string { return e.message } func (e *invalidParamsError) ErrorCode() int { return errCodeInvalidParams } + +type clientLimitExceededError struct{ message string } + +func (e *clientLimitExceededError) Error() string { return e.message } +func (e *clientLimitExceededError) ErrorCode() int { return errCodeClientLimitExceeded } + +type invalidBlockNumberError struct{ message string } + +func (e *invalidBlockNumberError) Error() string { return e.message } +func (e *invalidBlockNumberError) ErrorCode() int { return errCodeBlockNumberInvalid } + +type invalidBlockTimestampError struct{ message string } + +func (e *invalidBlockTimestampError) Error() string { return e.message } +func (e *invalidBlockTimestampError) ErrorCode() int { return errCodeBlockTimestampInvalid } + +type blockGasLimitReachedError struct{ message string } + +func (e *blockGasLimitReachedError) Error() string { return e.message } +func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached } diff --git a/internal/ethapi/multicall.go b/internal/ethapi/multicall.go index 44c79895ec..31395d612e 100644 --- a/internal/ethapi/multicall.go +++ b/internal/ethapi/multicall.go @@ -164,6 +164,13 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, nonce := state.GetNonce(call.from()) call.Nonce = (*hexutil.Uint64)(&nonce) } + var gas uint64 + if call.Gas != nil { + gas = uint64(*call.Gas) + } + if gasUsed+gas > blockContext.GasLimit { + return nil, &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} + } // Let the call run wild unless explicitly specified. if call.Gas == nil { remaining := blockContext.GasLimit - gasUsed @@ -187,9 +194,8 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, } result, err := applyMessage(ctx, mc.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { - callErr := callErrorFromError(err) - callResults[i] = mcCallResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} - continue + txErr := txValidationError(err) + return nil, txErr } // If the result contains a revert reason, try to unpack it. if len(result.Revert()) > 0 { @@ -200,9 +206,9 @@ func (mc *multicall) execute(ctx context.Context, opts mcOpts) ([]mcBlockResult, if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) if errors.Is(result.Err, vm.ErrExecutionReverted) { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeReverted} } else { - callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} + callRes.Error = &callError{Message: result.Err.Error(), Code: errCodeVMError} } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) @@ -327,7 +333,7 @@ func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Heade n := new(big.Int).Add(big.NewInt(int64(prevNumber)), big.NewInt(1)) overrides.Number = (*hexutil.Big)(n) } else if overrides.Number.ToInt().Uint64() <= prevNumber { - return nil, fmt.Errorf("block numbers must be in order") + return nil, &invalidBlockNumberError{fmt.Sprintf("block numbers must be in order: %d <= %d", overrides.Number.ToInt().Uint64(), prevNumber)} } prevNumber = overrides.Number.ToInt().Uint64() @@ -335,7 +341,7 @@ func makeHeaders(config *params.ChainConfig, blocks []mcBlock, base *types.Heade t := prevTimestamp + 1 overrides.Time = (*hexutil.Uint64)(&t) } else if time := (*uint64)(overrides.Time); *time <= prevTimestamp { - return nil, fmt.Errorf("timestamps must be in order") + return nil, &invalidBlockTimestampError{fmt.Sprintf("block timestamps must be in order: %d <= %d", *time, prevTimestamp)} } prevTimestamp = uint64(*overrides.Time)