Handle gracefully RPC calls when node is stopped (#1329)
This commit is contained in:
parent
b1f9030177
commit
08931fb761
|
@ -39,6 +39,9 @@ var (
|
||||||
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
||||||
// ErrUnsupportedRPCMethod is for methods not supported by the RPC interface
|
// ErrUnsupportedRPCMethod is for methods not supported by the RPC interface
|
||||||
ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface")
|
ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface")
|
||||||
|
// ErrRPCClientUnavailable is returned if an RPC client can't be retrieved.
|
||||||
|
// This is a normal situation when a node is stopped.
|
||||||
|
ErrRPCClientUnavailable = errors.New("JSON-RPC client is unavailable")
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusBackend implements Status.im service
|
// StatusBackend implements Status.im service
|
||||||
|
@ -215,15 +218,21 @@ func (b *StatusBackend) ResetChainData() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallRPC executes public RPC requests on node's in-proc RPC server.
|
// CallRPC executes public RPC requests on node's in-proc RPC server.
|
||||||
func (b *StatusBackend) CallRPC(inputJSON string) string {
|
func (b *StatusBackend) CallRPC(inputJSON string) (string, error) {
|
||||||
client := b.statusNode.RPCClient()
|
client := b.statusNode.RPCClient()
|
||||||
return client.CallRaw(inputJSON)
|
if client == nil {
|
||||||
|
return "", ErrRPCClientUnavailable
|
||||||
|
}
|
||||||
|
return client.CallRaw(inputJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
|
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
|
||||||
func (b *StatusBackend) CallPrivateRPC(inputJSON string) string {
|
func (b *StatusBackend) CallPrivateRPC(inputJSON string) (string, error) {
|
||||||
client := b.statusNode.RPCPrivateClient()
|
client := b.statusNode.RPCPrivateClient()
|
||||||
return client.CallRaw(inputJSON)
|
if client == nil {
|
||||||
|
return "", ErrRPCClientUnavailable
|
||||||
|
}
|
||||||
|
return client.CallRaw(inputJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTransaction creates a new transaction and waits until it's complete.
|
// SendTransaction creates a new transaction and waits until it's complete.
|
||||||
|
|
|
@ -228,20 +228,22 @@ func TestBackendCallRPCConcurrently(t *testing.T) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
result := backend.CallRPC(fmt.Sprintf(
|
result, err := backend.CallRPC(fmt.Sprintf(
|
||||||
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
|
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
|
||||||
idx+1,
|
idx+1,
|
||||||
))
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, result, "error")
|
assert.NotContains(t, result, "error")
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(i)
|
}(i)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(idx int) {
|
go func(idx int) {
|
||||||
result := backend.CallPrivateRPC(fmt.Sprintf(
|
result, err := backend.CallPrivateRPC(fmt.Sprintf(
|
||||||
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
|
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
|
||||||
idx+1,
|
idx+1,
|
||||||
))
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NotContains(t, result, "error")
|
assert.NotContains(t, result, "error")
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(i)
|
}(i)
|
||||||
|
@ -291,13 +293,30 @@ func TestBlockedRPCMethods(t *testing.T) {
|
||||||
defer func() { require.NoError(t, backend.StopNode()) }()
|
defer func() { require.NoError(t, backend.StopNode()) }()
|
||||||
|
|
||||||
for idx, m := range rpc.BlockedMethods() {
|
for idx, m := range rpc.BlockedMethods() {
|
||||||
result := backend.CallRPC(fmt.Sprintf(
|
result, err := backend.CallRPC(fmt.Sprintf(
|
||||||
`{"jsonrpc":"2.0","method":"%s","params":[],"id":%d}`,
|
`{"jsonrpc":"2.0","method":"%s","params":[],"id":%d}`,
|
||||||
m,
|
m,
|
||||||
idx+1,
|
idx+1,
|
||||||
))
|
))
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, result, fmt.Sprintf(`{"code":-32700,"message":"%s"}`, rpc.ErrMethodNotFound))
|
assert.Contains(t, result, fmt.Sprintf(`{"code":-32700,"message":"%s"}`, rpc.ErrMethodNotFound))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallRPCWithStoppedNode(t *testing.T) {
|
||||||
|
backend := NewStatusBackend()
|
||||||
|
|
||||||
|
resp, err := backend.CallRPC(
|
||||||
|
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}`,
|
||||||
|
)
|
||||||
|
assert.Equal(t, ErrRPCClientUnavailable, err)
|
||||||
|
assert.Equal(t, "", resp)
|
||||||
|
|
||||||
|
resp, err = backend.CallPrivateRPC(
|
||||||
|
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}`,
|
||||||
|
)
|
||||||
|
assert.Equal(t, ErrRPCClientUnavailable, err)
|
||||||
|
assert.Equal(t, "", resp)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(adam): add concurrent tests for: SendTransaction
|
// TODO(adam): add concurrent tests for: SendTransaction
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/status-im/status-go/services/typeddata"
|
"github.com/status-im/status-go/services/typeddata"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
"gopkg.in/go-playground/validator.v9"
|
validator "gopkg.in/go-playground/validator.v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// All general log messages in this package should be routed through this logger.
|
// All general log messages in this package should be routed through this logger.
|
||||||
|
@ -224,14 +224,20 @@ func ResetChainData() *C.char {
|
||||||
//CallRPC calls public APIs via RPC
|
//CallRPC calls public APIs via RPC
|
||||||
//export CallRPC
|
//export CallRPC
|
||||||
func CallRPC(inputJSON *C.char) *C.char {
|
func CallRPC(inputJSON *C.char) *C.char {
|
||||||
outputJSON := statusBackend.CallRPC(C.GoString(inputJSON))
|
outputJSON, err := statusBackend.CallRPC(C.GoString(inputJSON))
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
return C.CString(outputJSON)
|
return C.CString(outputJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
//CallPrivateRPC calls both public and private APIs via RPC
|
//CallPrivateRPC calls both public and private APIs via RPC
|
||||||
//export CallPrivateRPC
|
//export CallPrivateRPC
|
||||||
func CallPrivateRPC(inputJSON *C.char) *C.char {
|
func CallPrivateRPC(inputJSON *C.char) *C.char {
|
||||||
outputJSON := statusBackend.CallPrivateRPC(C.GoString(inputJSON))
|
outputJSON, err := statusBackend.CallPrivateRPC(C.GoString(inputJSON))
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
return C.CString(outputJSON)
|
return C.CString(outputJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,18 @@ func (s *BaseJSONRPCSuite) AssertAPIMethodExportedPrivately(method string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseJSONRPCSuite) isMethodExported(method string, private bool) bool {
|
func (s *BaseJSONRPCSuite) isMethodExported(method string, private bool) bool {
|
||||||
var result string
|
var (
|
||||||
|
result string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
cmd := fmt.Sprintf(`{"jsonrpc":"2.0", "method": "%s", "params": []}`, method)
|
cmd := fmt.Sprintf(`{"jsonrpc":"2.0", "method": "%s", "params": []}`, method)
|
||||||
if private {
|
if private {
|
||||||
result = s.Backend.CallPrivateRPC(cmd)
|
result, err = s.Backend.CallPrivateRPC(cmd)
|
||||||
} else {
|
} else {
|
||||||
result = s.Backend.CallRPC(cmd)
|
result, err = s.Backend.CallRPC(cmd)
|
||||||
}
|
}
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
var response struct {
|
var response struct {
|
||||||
Error *rpcError `json:"error"`
|
Error *rpcError `json:"error"`
|
||||||
|
|
|
@ -120,7 +120,9 @@ func (s *DebugAPISuite) sendPostConfirmMessage(symID string) string {
|
||||||
`{"jsonrpc":"2.0","method":"debug_postSync","params":[%s],"id":67}`,
|
`{"jsonrpc":"2.0","method":"debug_postSync","params":[%s],"id":67}`,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
return s.Backend.CallPrivateRPC(basicCall)
|
resp, err := s.Backend.CallPrivateRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPeers adds a peer to the running node
|
// addPeers adds a peer to the running node
|
||||||
|
|
|
@ -45,7 +45,8 @@ func (s *FiltersAPISuite) TestFilters() {
|
||||||
|
|
||||||
basicCall := `{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":67}`
|
basicCall := `{"jsonrpc":"2.0","method":"eth_newBlockFilter","params":[],"id":67}`
|
||||||
|
|
||||||
response := s.Backend.CallRPC(basicCall)
|
response, err := s.Backend.CallRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
filterID := s.filterIDFromRPCResponse(response)
|
filterID := s.filterIDFromRPCResponse(response)
|
||||||
|
|
||||||
// we don't check new blocks on private network, because no one mines them
|
// we don't check new blocks on private network, because no one mines them
|
||||||
|
@ -65,7 +66,8 @@ func (s *FiltersAPISuite) TestFilters() {
|
||||||
|
|
||||||
basicCall = fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_uninstallFilter","params":["%s"],"id":67}`, filterID)
|
basicCall = fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_uninstallFilter","params":["%s"],"id":67}`, filterID)
|
||||||
|
|
||||||
response = s.Backend.CallRPC(basicCall)
|
response, err = s.Backend.CallRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
result := s.boolFromRPCResponse(response)
|
result := s.boolFromRPCResponse(response)
|
||||||
|
|
||||||
s.True(result, "filter expected to be removed successfully")
|
s.True(result, "filter expected to be removed successfully")
|
||||||
|
@ -79,7 +81,8 @@ func (s *FiltersAPISuite) getFirstFilterChange(filterID string) chan string {
|
||||||
timeout := time.Now().Add(time.Minute)
|
timeout := time.Now().Add(time.Minute)
|
||||||
for time.Now().Before(timeout) {
|
for time.Now().Before(timeout) {
|
||||||
basicCall := fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["%s"],"id":67}`, filterID)
|
basicCall := fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getFilterChanges","params":["%s"],"id":67}`, filterID)
|
||||||
response := s.Backend.CallRPC(basicCall)
|
response, err := s.Backend.CallRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
filterChanges := s.arrayFromRPCResponse(response)
|
filterChanges := s.arrayFromRPCResponse(response)
|
||||||
if len(filterChanges) > 0 {
|
if len(filterChanges) > 0 {
|
||||||
result <- filterChanges[0]
|
result <- filterChanges[0]
|
||||||
|
|
|
@ -67,8 +67,8 @@ func (s *PersonalSignSuite) TestPersonalSignUnsupportedMethod() {
|
||||||
signDataString,
|
signDataString,
|
||||||
TestConfig.Account1.Address)
|
TestConfig.Account1.Address)
|
||||||
|
|
||||||
rawResult := s.Backend.CallRPC(basicCall)
|
rawResult, err := s.Backend.CallRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ func (s *PersonalSignSuite) TestPersonalRecoverUnsupportedMethod() {
|
||||||
signDataString,
|
signDataString,
|
||||||
"")
|
"")
|
||||||
|
|
||||||
rawResult := s.Backend.CallRPC(basicCall)
|
rawResult, err := s.Backend.CallRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,8 @@ func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.Lo
|
||||||
`{"jsonrpc":"2.0","method":"status_login","params":[%s],"id":67}`,
|
`{"jsonrpc":"2.0","method":"status_login","params":[%s],"id":67}`,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
result := s.Backend.CallPrivateRPC(basicCall)
|
result, err := s.Backend.CallPrivateRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
if testParams.ExpectedError == nil {
|
if testParams.ExpectedError == nil {
|
||||||
var r struct {
|
var r struct {
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
|
@ -164,7 +165,8 @@ func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.S
|
||||||
`{"jsonrpc":"2.0","method":"status_signup","params":[%s],"id":67}`,
|
`{"jsonrpc":"2.0","method":"status_signup","params":[%s],"id":67}`,
|
||||||
body)
|
body)
|
||||||
|
|
||||||
result := s.Backend.CallPrivateRPC(basicCall)
|
result, err := s.Backend.CallPrivateRPC(basicCall)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
if testParams.ExpectedError == nil {
|
if testParams.ExpectedError == nil {
|
||||||
var r struct {
|
var r struct {
|
||||||
|
|
|
@ -67,11 +67,13 @@ func (s *TransactionsTestSuite) TestCallUpstreamPrivateRPCSendTransaction() {
|
||||||
s.sendTransactionUsingRPCClient(s.Backend.CallPrivateRPC)
|
s.sendTransactionUsingRPCClient(s.Backend.CallPrivateRPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransactionsTestSuite) sendTransactionUsingRPCClient(callRPCFn func(string) string) {
|
func (s *TransactionsTestSuite) sendTransactionUsingRPCClient(
|
||||||
|
callRPCFn func(string) (string, error),
|
||||||
|
) {
|
||||||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
result := callRPCFn(`{
|
result, err := callRPCFn(`{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "eth_sendTransaction",
|
"method": "eth_sendTransaction",
|
||||||
|
@ -81,6 +83,7 @@ func (s *TransactionsTestSuite) sendTransactionUsingRPCClient(callRPCFn func(str
|
||||||
"value": "0x9184e72a"
|
"value": "0x9184e72a"
|
||||||
}]
|
}]
|
||||||
}`)
|
}`)
|
||||||
|
s.NoError(err)
|
||||||
s.Contains(result, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
s.Contains(result, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue