internal/ethapi: return revert reason for eth_call (#21083)
* internal/ethapi: return revert reason for eth_call * internal/ethapi: moved revert reason logic to doCall * accounts/abi/bind/backends: added revert reason logic to simulated backend * internal/ethapi: fixed linting error * internal/ethapi: check if require reason can be unpacked * internal/ethapi: better error logic * internal/ethapi: simplify logic * internal/ethapi: return vmError() * internal/ethapi: move handling of revert out of docall * graphql: removed revert logic until spec change * rpc: internal/ethapi: added custom error types * graphql: use returndata instead of return Return() checks if there is an error. If an error is found, we return nil. For most use cases it can be beneficial to return the output even if there was an error. This code should be changed anyway once the spec supports error reasons in graphql responses * accounts/abi/bind/backends: added tests for revert reason * internal/ethapi: add errorCode to revert error * internal/ethapi: add errorCode of 3 to revertError * internal/ethapi: unified estimateGasErrors, simplified logic * internal/ethapi: unified handling of errors in DoEstimateGas * rpc: print error data field * accounts/abi/bind/backends: unify simulatedBackend and RPC * internal/ethapi: added binary data to revertError data * internal/ethapi: refactored unpacking logic into newRevertError * accounts/abi/bind/backends: fix EstimateGas * accounts, console, internal, rpc: minor error interface cleanups * Revert "accounts, console, internal, rpc: minor error interface cleanups" This reverts commit 2d3ef53c5304e429a04983210a417c1f4e0dafb7. * re-apply the good parts of 2d3ef53c53 * rpc: add test for returning server error data from client Co-authored-by: rjl493456442 <garyrong0905@gmail.com> Co-authored-by: Péter Szilágyi <peterke@gmail.com> Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
88125d8bd0
commit
0b3f3be2b5
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
@ -344,6 +345,36 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad
|
|||||||
return b.pendingState.GetCode(contract), nil
|
return b.pendingState.GetCode(contract), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRevertError(result *core.ExecutionResult) *revertError {
|
||||||
|
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
||||||
|
err := errors.New("execution reverted")
|
||||||
|
if errUnpack == nil {
|
||||||
|
err = fmt.Errorf("execution reverted: %v", reason)
|
||||||
|
}
|
||||||
|
return &revertError{
|
||||||
|
error: err,
|
||||||
|
reason: hexutil.Encode(result.Revert()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revertError is an API error that encompassas an EVM revertal with JSON error
|
||||||
|
// code and a binary data blob.
|
||||||
|
type revertError struct {
|
||||||
|
error
|
||||||
|
reason string // revert reason hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode returns the JSON error code for a revertal.
|
||||||
|
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
||||||
|
func (e *revertError) ErrorCode() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorData returns the hex encoded revert reason.
|
||||||
|
func (e *revertError) ErrorData() interface{} {
|
||||||
|
return e.reason
|
||||||
|
}
|
||||||
|
|
||||||
// CallContract executes a contract call.
|
// CallContract executes a contract call.
|
||||||
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
@ -360,7 +391,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res.Return(), nil
|
// If the result contains a revert reason, try to unpack and return it.
|
||||||
|
if len(res.Revert()) > 0 {
|
||||||
|
return nil, newRevertError(res)
|
||||||
|
}
|
||||||
|
return res.Return(), res.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingCallContract executes a contract call on the pending state.
|
// PendingCallContract executes a contract call on the pending state.
|
||||||
@ -373,7 +408,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res.Return(), nil
|
// If the result contains a revert reason, try to unpack and return it.
|
||||||
|
if len(res.Revert()) > 0 {
|
||||||
|
return nil, newRevertError(res)
|
||||||
|
}
|
||||||
|
return res.Return(), res.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
||||||
@ -472,16 +511,10 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
|||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
|
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
ret, err := abi.UnpackRevert(result.Revert())
|
return 0, newRevertError(result)
|
||||||
if err != nil {
|
|
||||||
errMsg += fmt.Sprintf(" (%#x)", result.Revert())
|
|
||||||
} else {
|
|
||||||
errMsg += fmt.Sprintf(" (%s)", ret)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0, errors.New(errMsg)
|
return 0, result.Err
|
||||||
}
|
}
|
||||||
// Otherwise, the specified gas cap is too low
|
// Otherwise, the specified gas cap is too low
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -106,14 +107,18 @@ const deployedCode = `60806040526004361061003b576000357c010000000000000000000000
|
|||||||
// expected return value contains "hello world"
|
// expected return value contains "hello world"
|
||||||
var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
|
||||||
|
func simTestBackend(testAddr common.Address) *SimulatedBackend {
|
||||||
|
return NewSimulatedBackend(
|
||||||
|
core.GenesisAlloc{
|
||||||
|
testAddr: {Balance: big.NewInt(10000000000)},
|
||||||
|
}, 10000000,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewSimulatedBackend(t *testing.T) {
|
func TestNewSimulatedBackend(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
expectedBal := big.NewInt(10000000000)
|
expectedBal := big.NewInt(10000000000)
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: expectedBal},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
if sim.config != params.AllEthashProtocolChanges {
|
if sim.config != params.AllEthashProtocolChanges {
|
||||||
@ -152,11 +157,7 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) {
|
|||||||
func TestSimulatedBackend_BalanceAt(t *testing.T) {
|
func TestSimulatedBackend_BalanceAt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
expectedBal := big.NewInt(10000000000)
|
expectedBal := big.NewInt(10000000000)
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: expectedBal},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -229,11 +230,7 @@ func TestSimulatedBackend_BlockByNumber(t *testing.T) {
|
|||||||
func TestSimulatedBackend_NonceAt(t *testing.T) {
|
func TestSimulatedBackend_NonceAt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -283,11 +280,7 @@ func TestSimulatedBackend_NonceAt(t *testing.T) {
|
|||||||
func TestSimulatedBackend_SendTransaction(t *testing.T) {
|
func TestSimulatedBackend_SendTransaction(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -395,6 +388,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
message ethereum.CallMsg
|
message ethereum.CallMsg
|
||||||
expect uint64
|
expect uint64
|
||||||
expectError error
|
expectError error
|
||||||
|
expectData interface{}
|
||||||
}{
|
}{
|
||||||
{"plain transfer(valid)", ethereum.CallMsg{
|
{"plain transfer(valid)", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -403,7 +397,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: big.NewInt(1),
|
Value: big.NewInt(1),
|
||||||
Data: nil,
|
Data: nil,
|
||||||
}, params.TxGas, nil},
|
}, params.TxGas, nil, nil},
|
||||||
|
|
||||||
{"plain transfer(invalid)", ethereum.CallMsg{
|
{"plain transfer(invalid)", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -412,7 +406,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: big.NewInt(1),
|
Value: big.NewInt(1),
|
||||||
Data: nil,
|
Data: nil,
|
||||||
}, 0, errors.New("always failing transaction (execution reverted)")},
|
}, 0, errors.New("execution reverted"), nil},
|
||||||
|
|
||||||
{"Revert", ethereum.CallMsg{
|
{"Revert", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -421,7 +415,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("d8b98391"),
|
Data: common.Hex2Bytes("d8b98391"),
|
||||||
}, 0, errors.New("always failing transaction (execution reverted) (revert reason)")},
|
}, 0, errors.New("execution reverted: revert reason"), "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"},
|
||||||
|
|
||||||
{"PureRevert", ethereum.CallMsg{
|
{"PureRevert", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -430,7 +424,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("aa8b1d30"),
|
Data: common.Hex2Bytes("aa8b1d30"),
|
||||||
}, 0, errors.New("always failing transaction (execution reverted)")},
|
}, 0, errors.New("execution reverted"), nil},
|
||||||
|
|
||||||
{"OOG", ethereum.CallMsg{
|
{"OOG", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -439,7 +433,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("50f6fe34"),
|
Data: common.Hex2Bytes("50f6fe34"),
|
||||||
}, 0, errors.New("gas required exceeds allowance (100000)")},
|
}, 0, errors.New("gas required exceeds allowance (100000)"), nil},
|
||||||
|
|
||||||
{"Assert", ethereum.CallMsg{
|
{"Assert", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -448,7 +442,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("b9b046f9"),
|
Data: common.Hex2Bytes("b9b046f9"),
|
||||||
}, 0, errors.New("always failing transaction (invalid opcode: opcode 0xfe not defined)")},
|
}, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil},
|
||||||
|
|
||||||
{"Valid", ethereum.CallMsg{
|
{"Valid", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
@ -457,7 +451,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("e09fface"),
|
Data: common.Hex2Bytes("e09fface"),
|
||||||
}, 21275, nil},
|
}, 21275, nil, nil},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
got, err := sim.EstimateGas(context.Background(), c.message)
|
got, err := sim.EstimateGas(context.Background(), c.message)
|
||||||
@ -468,6 +462,13 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
if c.expectError.Error() != err.Error() {
|
if c.expectError.Error() != err.Error() {
|
||||||
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
|
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
|
||||||
}
|
}
|
||||||
|
if c.expectData != nil {
|
||||||
|
if err, ok := err.(*revertError); !ok {
|
||||||
|
t.Fatalf("Expect revert error, got %T", err)
|
||||||
|
} else if !reflect.DeepEqual(err.ErrorData(), c.expectData) {
|
||||||
|
t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData())
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if got != c.expect {
|
if got != c.expect {
|
||||||
@ -546,11 +547,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
|||||||
func TestSimulatedBackend_HeaderByHash(t *testing.T) {
|
func TestSimulatedBackend_HeaderByHash(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -571,11 +568,7 @@ func TestSimulatedBackend_HeaderByHash(t *testing.T) {
|
|||||||
func TestSimulatedBackend_HeaderByNumber(t *testing.T) {
|
func TestSimulatedBackend_HeaderByNumber(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -622,11 +615,7 @@ func TestSimulatedBackend_HeaderByNumber(t *testing.T) {
|
|||||||
func TestSimulatedBackend_TransactionCount(t *testing.T) {
|
func TestSimulatedBackend_TransactionCount(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
currentBlock, err := sim.BlockByNumber(bgCtx, nil)
|
currentBlock, err := sim.BlockByNumber(bgCtx, nil)
|
||||||
@ -676,11 +665,7 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) {
|
|||||||
func TestSimulatedBackend_TransactionInBlock(t *testing.T) {
|
func TestSimulatedBackend_TransactionInBlock(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -743,11 +728,7 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) {
|
|||||||
func TestSimulatedBackend_PendingNonceAt(t *testing.T) {
|
func TestSimulatedBackend_PendingNonceAt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -809,11 +790,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) {
|
|||||||
func TestSimulatedBackend_TransactionReceipt(t *testing.T) {
|
func TestSimulatedBackend_TransactionReceipt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
}, 10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -859,12 +836,7 @@ func TestSimulatedBackend_SuggestGasPrice(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimulatedBackend_PendingCodeAt(t *testing.T) {
|
func TestSimulatedBackend_PendingCodeAt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
},
|
|
||||||
10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
code, err := sim.CodeAt(bgCtx, testAddr, nil)
|
code, err := sim.CodeAt(bgCtx, testAddr, nil)
|
||||||
@ -900,12 +872,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimulatedBackend_CodeAt(t *testing.T) {
|
func TestSimulatedBackend_CodeAt(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
},
|
|
||||||
10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
code, err := sim.CodeAt(bgCtx, testAddr, nil)
|
code, err := sim.CodeAt(bgCtx, testAddr, nil)
|
||||||
@ -944,12 +911,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) {
|
|||||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
func TestSimulatedBackend_PendingAndCallContract(t *testing.T) {
|
func TestSimulatedBackend_PendingAndCallContract(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
sim := NewSimulatedBackend(
|
sim := simTestBackend(testAddr)
|
||||||
core.GenesisAlloc{
|
|
||||||
testAddr: {Balance: big.NewInt(10000000000)},
|
|
||||||
},
|
|
||||||
10000000,
|
|
||||||
)
|
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
@ -965,7 +927,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) {
|
|||||||
|
|
||||||
input, err := parsed.Pack("receive", []byte("X"))
|
input, err := parsed.Pack("receive", []byte("X"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could pack receive function on contract: %v", err)
|
t.Errorf("could not pack receive function on contract: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure you can call the contract in pending state
|
// make sure you can call the contract in pending state
|
||||||
@ -1005,3 +967,113 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) {
|
|||||||
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test is based on the following contract:
|
||||||
|
/*
|
||||||
|
contract Reverter {
|
||||||
|
function revertString() public pure{
|
||||||
|
require(false, "some error");
|
||||||
|
}
|
||||||
|
function revertNoString() public pure {
|
||||||
|
require(false, "");
|
||||||
|
}
|
||||||
|
function revertASM() public pure {
|
||||||
|
assembly {
|
||||||
|
revert(0x0, 0x0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function noRevert() public pure {
|
||||||
|
assembly {
|
||||||
|
// Assembles something that looks like require(false, "some error") but is not reverted
|
||||||
|
mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||||
|
mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020)
|
||||||
|
mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a)
|
||||||
|
mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000)
|
||||||
|
return(0x0, 0x64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
func TestSimulatedBackend_CallContractRevert(t *testing.T) {
|
||||||
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
sim := simTestBackend(testAddr)
|
||||||
|
defer sim.Close()
|
||||||
|
bgCtx := context.Background()
|
||||||
|
|
||||||
|
reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]`
|
||||||
|
reverterBin := "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033"
|
||||||
|
|
||||||
|
parsed, err := abi.JSON(strings.NewReader(reverterABI))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not get code at test addr: %v", err)
|
||||||
|
}
|
||||||
|
contractAuth := bind.NewKeyedTransactor(testKey)
|
||||||
|
addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not deploy contract: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := make(map[string]interface{}, 3)
|
||||||
|
inputs["revertASM"] = nil
|
||||||
|
inputs["revertNoString"] = ""
|
||||||
|
inputs["revertString"] = "some error"
|
||||||
|
|
||||||
|
call := make([]func([]byte) ([]byte, error), 2)
|
||||||
|
call[0] = func(input []byte) ([]byte, error) {
|
||||||
|
return sim.PendingCallContract(bgCtx, ethereum.CallMsg{
|
||||||
|
From: testAddr,
|
||||||
|
To: &addr,
|
||||||
|
Data: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
call[1] = func(input []byte) ([]byte, error) {
|
||||||
|
return sim.CallContract(bgCtx, ethereum.CallMsg{
|
||||||
|
From: testAddr,
|
||||||
|
To: &addr,
|
||||||
|
Data: input,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run pending calls then commit
|
||||||
|
for _, cl := range call {
|
||||||
|
for key, val := range inputs {
|
||||||
|
input, err := parsed.Pack(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not pack %v function on contract: %v", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := cl(input)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("call to %v was not reverted", key)
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
t.Errorf("result from %v was not nil: %v", key, res)
|
||||||
|
}
|
||||||
|
if val != nil {
|
||||||
|
rerr, ok := err.(*revertError)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expect revert error")
|
||||||
|
}
|
||||||
|
if rerr.Error() != "execution reverted: "+val.(string) {
|
||||||
|
t.Errorf("error was malformed: got %v want %v", rerr.Error(), val)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// revert(0x0,0x0)
|
||||||
|
if err.Error() != "execution reverted" {
|
||||||
|
t.Errorf("error was malformed: got %v want %v", err, "execution reverted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input, err := parsed.Pack("noRevert")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not pack noRevert function on contract: %v", err)
|
||||||
|
}
|
||||||
|
res, err := cl(input)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("call to noRevert was reverted")
|
||||||
|
}
|
||||||
|
if res == nil {
|
||||||
|
t.Errorf("result from noRevert was nil")
|
||||||
|
}
|
||||||
|
sim.Commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -413,9 +413,7 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
|
|||||||
resp.Set("id", req.ID)
|
resp.Set("id", req.ID)
|
||||||
|
|
||||||
var result json.RawMessage
|
var result json.RawMessage
|
||||||
err = b.client.Call(&result, req.Method, req.Params...)
|
if err = b.client.Call(&result, req.Method, req.Params...); err == nil {
|
||||||
switch err := err.(type) {
|
|
||||||
case nil:
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
// Special case null because it is decoded as an empty
|
// Special case null because it is decoded as an empty
|
||||||
// raw message for some reason.
|
// raw message for some reason.
|
||||||
@ -428,19 +426,24 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
|
|||||||
}
|
}
|
||||||
resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result)))
|
resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setError(resp, -32603, err.Error())
|
setError(resp, -32603, err.Error(), nil)
|
||||||
} else {
|
} else {
|
||||||
resp.Set("result", resultVal)
|
resp.Set("result", resultVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case rpc.Error:
|
} else {
|
||||||
setError(resp, err.ErrorCode(), err.Error())
|
code := -32603
|
||||||
default:
|
var data interface{}
|
||||||
setError(resp, -32603, err.Error())
|
if err, ok := err.(rpc.Error); ok {
|
||||||
|
code = err.ErrorCode()
|
||||||
|
}
|
||||||
|
if err, ok := err.(rpc.DataError); ok {
|
||||||
|
data = err.ErrorData()
|
||||||
|
}
|
||||||
|
setError(resp, code, err.Error(), data)
|
||||||
}
|
}
|
||||||
resps = append(resps, resp)
|
resps = append(resps, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the responses either to the callback (if supplied)
|
// Return the responses either to the callback (if supplied)
|
||||||
// or directly as the return value.
|
// or directly as the return value.
|
||||||
var result goja.Value
|
var result goja.Value
|
||||||
@ -456,8 +459,14 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setError(resp *goja.Object, code int, msg string) {
|
func setError(resp *goja.Object, code int, msg string, data interface{}) {
|
||||||
resp.Set("error", map[string]interface{}{"code": code, "message": msg})
|
err := make(map[string]interface{})
|
||||||
|
err["code"] = code
|
||||||
|
err["message"] = msg
|
||||||
|
if data != nil {
|
||||||
|
err["data"] = data
|
||||||
|
}
|
||||||
|
resp.Set("error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNumber returns true if input value is a JS number.
|
// isNumber returns true if input value is a JS number.
|
||||||
|
@ -811,8 +811,9 @@ func (b *Block) Call(ctx context.Context, args struct {
|
|||||||
if result.Failed() {
|
if result.Failed() {
|
||||||
status = 0
|
status = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CallResult{
|
return &CallResult{
|
||||||
data: result.Return(),
|
data: result.ReturnData,
|
||||||
gasUsed: hexutil.Uint64(result.UsedGas),
|
gasUsed: hexutil.Uint64(result.UsedGas),
|
||||||
status: status,
|
status: status,
|
||||||
}, nil
|
}, nil
|
||||||
@ -880,8 +881,9 @@ func (p *Pending) Call(ctx context.Context, args struct {
|
|||||||
if result.Failed() {
|
if result.Failed() {
|
||||||
status = 0
|
status = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CallResult{
|
return &CallResult{
|
||||||
data: result.Return(),
|
data: result.ReturnData,
|
||||||
gasUsed: hexutil.Uint64(result.UsedGas),
|
gasUsed: hexutil.Uint64(result.UsedGas),
|
||||||
status: status,
|
status: status,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -864,6 +864,36 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRevertError(result *core.ExecutionResult) *revertError {
|
||||||
|
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
||||||
|
err := errors.New("execution reverted")
|
||||||
|
if errUnpack == nil {
|
||||||
|
err = fmt.Errorf("execution reverted: %v", reason)
|
||||||
|
}
|
||||||
|
return &revertError{
|
||||||
|
error: err,
|
||||||
|
reason: hexutil.Encode(result.Revert()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// revertError is an API error that encompassas an EVM revertal with JSON error
|
||||||
|
// code and a binary data blob.
|
||||||
|
type revertError struct {
|
||||||
|
error
|
||||||
|
reason string // revert reason hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode returns the JSON error code for a revertal.
|
||||||
|
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
||||||
|
func (e *revertError) ErrorCode() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorData returns the hex encoded revert reason.
|
||||||
|
func (e *revertError) ErrorData() interface{} {
|
||||||
|
return e.reason
|
||||||
|
}
|
||||||
|
|
||||||
// Call executes the given transaction on the state for the given block number.
|
// Call executes the given transaction on the state for the given block number.
|
||||||
//
|
//
|
||||||
// Additionally, the caller can specify a batch of contract for fields overriding.
|
// Additionally, the caller can specify a batch of contract for fields overriding.
|
||||||
@ -879,24 +909,11 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result.Return(), nil
|
// If the result contains a revert reason, try to unpack and return it.
|
||||||
}
|
if len(result.Revert()) > 0 {
|
||||||
|
return nil, newRevertError(result)
|
||||||
type estimateGasError struct {
|
|
||||||
error string // Concrete error type if it's failed to estimate gas usage
|
|
||||||
vmerr error // Additional field, it's non-nil if the given transaction is invalid
|
|
||||||
revert string // Additional field, it's non-empty if the transaction is reverted and reason is provided
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e estimateGasError) Error() string {
|
|
||||||
errMsg := e.error
|
|
||||||
if e.vmerr != nil {
|
|
||||||
errMsg += fmt.Sprintf(" (%v)", e.vmerr)
|
|
||||||
}
|
}
|
||||||
if e.revert != "" {
|
return result.Return(), result.Err
|
||||||
errMsg += fmt.Sprintf(" (%s)", e.revert)
|
|
||||||
}
|
|
||||||
return errMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
||||||
@ -991,23 +1008,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
|||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
var revert string
|
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
ret, err := abi.UnpackRevert(result.Revert())
|
return 0, newRevertError(result)
|
||||||
if err != nil {
|
|
||||||
revert = hexutil.Encode(result.Revert())
|
|
||||||
} else {
|
|
||||||
revert = ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, estimateGasError{
|
|
||||||
error: "always failing transaction",
|
|
||||||
vmerr: result.Err,
|
|
||||||
revert: revert,
|
|
||||||
}
|
}
|
||||||
|
return 0, result.Err
|
||||||
}
|
}
|
||||||
// Otherwise, the specified gas cap is too low
|
// Otherwise, the specified gas cap is too low
|
||||||
return 0, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)}
|
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hexutil.Uint64(hi), nil
|
return hexutil.Uint64(hi), nil
|
||||||
|
@ -66,6 +66,33 @@ func TestClientResponseType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test checks that server-returned errors with code and data come out of Client.Call.
|
||||||
|
func TestClientErrorData(t *testing.T) {
|
||||||
|
server := newTestServer()
|
||||||
|
defer server.Stop()
|
||||||
|
client := DialInProc(server)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
var resp interface{}
|
||||||
|
err := client.Call(&resp, "test_returnError")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check code.
|
||||||
|
if e, ok := err.(Error); !ok {
|
||||||
|
t.Fatalf("client did not return rpc.Error, got %#v", e)
|
||||||
|
} else if e.ErrorCode() != (testError{}.ErrorCode()) {
|
||||||
|
t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
|
||||||
|
}
|
||||||
|
// Check data.
|
||||||
|
if e, ok := err.(DataError); !ok {
|
||||||
|
t.Fatalf("client did not return rpc.DataError, got %#v", e)
|
||||||
|
} else if e.ErrorData() != (testError{}.ErrorData()) {
|
||||||
|
t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientBatchRequest(t *testing.T) {
|
func TestClientBatchRequest(t *testing.T) {
|
||||||
server := newTestServer()
|
server := newTestServer()
|
||||||
defer server.Stop()
|
defer server.Stop()
|
||||||
|
@ -18,6 +18,15 @@ package rpc
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Error = new(methodNotFoundError)
|
||||||
|
_ Error = new(subscriptionNotFoundError)
|
||||||
|
_ Error = new(parseError)
|
||||||
|
_ Error = new(invalidRequestError)
|
||||||
|
_ Error = new(invalidMessageError)
|
||||||
|
_ Error = new(invalidParamsError)
|
||||||
|
)
|
||||||
|
|
||||||
const defaultErrorCode = -32000
|
const defaultErrorCode = -32000
|
||||||
|
|
||||||
type methodNotFoundError struct{ method string }
|
type methodNotFoundError struct{ method string }
|
||||||
|
@ -296,10 +296,16 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess
|
|||||||
return nil
|
return nil
|
||||||
case msg.isCall():
|
case msg.isCall():
|
||||||
resp := h.handleCall(ctx, msg)
|
resp := h.handleCall(ctx, msg)
|
||||||
|
var ctx []interface{}
|
||||||
|
ctx = append(ctx, "reqid", idForLog{msg.ID}, "t", time.Since(start))
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
h.log.Warn("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start), "err", resp.Error.Message)
|
ctx = append(ctx, "err", resp.Error.Message)
|
||||||
|
if resp.Error.Data != nil {
|
||||||
|
ctx = append(ctx, "errdata", resp.Error.Data)
|
||||||
|
}
|
||||||
|
h.log.Warn("Served "+msg.Method, ctx...)
|
||||||
} else {
|
} else {
|
||||||
h.log.Debug("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start))
|
h.log.Debug("Served "+msg.Method, ctx...)
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
case msg.hasValidID():
|
case msg.hasValidID():
|
||||||
|
@ -115,6 +115,10 @@ func errorMessage(err error) *jsonrpcMessage {
|
|||||||
if ok {
|
if ok {
|
||||||
msg.Error.Code = ec.ErrorCode()
|
msg.Error.Code = ec.ErrorCode()
|
||||||
}
|
}
|
||||||
|
de, ok := err.(DataError)
|
||||||
|
if ok {
|
||||||
|
msg.Error.Data = de.ErrorData()
|
||||||
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +139,10 @@ func (err *jsonError) ErrorCode() int {
|
|||||||
return err.Code
|
return err.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (err *jsonError) ErrorData() interface{} {
|
||||||
|
return err.Data
|
||||||
|
}
|
||||||
|
|
||||||
// Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec.
|
// Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec.
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
|
@ -45,7 +45,7 @@ func TestServerRegisterName(t *testing.T) {
|
|||||||
t.Fatalf("Expected service calc to be registered")
|
t.Fatalf("Expected service calc to be registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCallbacks := 8
|
wantCallbacks := 9
|
||||||
if len(svc.callbacks) != wantCallbacks {
|
if len(svc.callbacks) != wantCallbacks {
|
||||||
t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks))
|
t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks))
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,12 @@ type echoResult struct {
|
|||||||
Args *echoArgs
|
Args *echoArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testError struct{}
|
||||||
|
|
||||||
|
func (testError) Error() string { return "testError" }
|
||||||
|
func (testError) ErrorCode() int { return 444 }
|
||||||
|
func (testError) ErrorData() interface{} { return "testError data" }
|
||||||
|
|
||||||
func (s *testService) NoArgsRets() {}
|
func (s *testService) NoArgsRets() {}
|
||||||
|
|
||||||
func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
|
func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
|
||||||
@ -99,6 +105,10 @@ func (s *testService) InvalidRets3() (string, string, error) {
|
|||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *testService) ReturnError() error {
|
||||||
|
return testError{}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) {
|
func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) {
|
||||||
c, ok := ClientFromContext(ctx)
|
c, ok := ClientFromContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -41,6 +41,12 @@ type Error interface {
|
|||||||
ErrorCode() int // returns the code
|
ErrorCode() int // returns the code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A DataError contains some data in addition to the error message.
|
||||||
|
type DataError interface {
|
||||||
|
Error() string // returns the message
|
||||||
|
ErrorData() interface{} // returns the error data
|
||||||
|
}
|
||||||
|
|
||||||
// ServerCodec implements reading, parsing and writing RPC messages for the server side of
|
// ServerCodec implements reading, parsing and writing RPC messages for the server side of
|
||||||
// a RPC session. Implementations must be go-routine safe since the codec can be called in
|
// a RPC session. Implementations must be go-routine safe since the codec can be called in
|
||||||
// multiple go-routines concurrently.
|
// multiple go-routines concurrently.
|
||||||
|
Loading…
Reference in New Issue
Block a user