Handle gracefully RPC calls when node is stopped (#1329)

This commit is contained in:
Adam Babik 2018-12-20 09:31:17 +01:00 committed by GitHub
parent b1f9030177
commit 08931fb761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 25 deletions

View File

@ -39,6 +39,9 @@ var (
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
// ErrUnsupportedRPCMethod is for methods not supported by the 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
@ -215,15 +218,21 @@ func (b *StatusBackend) ResetChainData() error {
}
// 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()
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.
func (b *StatusBackend) CallPrivateRPC(inputJSON string) string {
func (b *StatusBackend) CallPrivateRPC(inputJSON string) (string, error) {
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.

View File

@ -228,20 +228,22 @@ func TestBackendCallRPCConcurrently(t *testing.T) {
for i := 0; i < count; i++ {
wg.Add(1)
go func(idx int) {
result := backend.CallRPC(fmt.Sprintf(
result, err := backend.CallRPC(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
idx+1,
))
assert.NoError(t, err)
assert.NotContains(t, result, "error")
wg.Done()
}(i)
wg.Add(1)
go func(idx int) {
result := backend.CallPrivateRPC(fmt.Sprintf(
result, err := backend.CallPrivateRPC(fmt.Sprintf(
`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`,
idx+1,
))
assert.NoError(t, err)
assert.NotContains(t, result, "error")
wg.Done()
}(i)
@ -291,13 +293,30 @@ func TestBlockedRPCMethods(t *testing.T) {
defer func() { require.NoError(t, backend.StopNode()) }()
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}`,
m,
idx+1,
))
assert.NoError(t, err)
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

View File

@ -17,7 +17,7 @@ import (
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/signal"
"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.
@ -224,14 +224,20 @@ func ResetChainData() *C.char {
//CallRPC calls public APIs via RPC
//export CallRPC
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)
}
//CallPrivateRPC calls both public and private APIs via RPC
//export CallPrivateRPC
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)
}

View File

@ -45,14 +45,18 @@ func (s *BaseJSONRPCSuite) AssertAPIMethodExportedPrivately(method string) {
}
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)
if private {
result = s.Backend.CallPrivateRPC(cmd)
result, err = s.Backend.CallPrivateRPC(cmd)
} else {
result = s.Backend.CallRPC(cmd)
result, err = s.Backend.CallRPC(cmd)
}
s.NoError(err)
var response struct {
Error *rpcError `json:"error"`

View File

@ -120,7 +120,9 @@ func (s *DebugAPISuite) sendPostConfirmMessage(symID string) string {
`{"jsonrpc":"2.0","method":"debug_postSync","params":[%s],"id":67}`,
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

View File

@ -45,7 +45,8 @@ func (s *FiltersAPISuite) TestFilters() {
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)
// 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)
response = s.Backend.CallRPC(basicCall)
response, err = s.Backend.CallRPC(basicCall)
s.NoError(err)
result := s.boolFromRPCResponse(response)
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)
for time.Now().Before(timeout) {
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)
if len(filterChanges) > 0 {
result <- filterChanges[0]

View File

@ -67,8 +67,8 @@ func (s *PersonalSignSuite) TestPersonalSignUnsupportedMethod() {
signDataString,
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"}`)
}
@ -93,7 +93,7 @@ func (s *PersonalSignSuite) TestPersonalRecoverUnsupportedMethod() {
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"}`)
}

View File

@ -125,7 +125,8 @@ func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.Lo
`{"jsonrpc":"2.0","method":"status_login","params":[%s],"id":67}`,
body)
result := s.Backend.CallPrivateRPC(basicCall)
result, err := s.Backend.CallPrivateRPC(basicCall)
s.NoError(err)
if testParams.ExpectedError == nil {
var r struct {
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}`,
body)
result := s.Backend.CallPrivateRPC(basicCall)
result, err := s.Backend.CallPrivateRPC(basicCall)
s.NoError(err)
if testParams.ExpectedError == nil {
var r struct {

View File

@ -67,11 +67,13 @@ func (s *TransactionsTestSuite) TestCallUpstreamPrivateRPCSendTransaction() {
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)
s.NoError(err)
result := callRPCFn(`{
result, err := callRPCFn(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendTransaction",
@ -81,6 +83,7 @@ func (s *TransactionsTestSuite) sendTransactionUsingRPCClient(callRPCFn func(str
"value": "0x9184e72a"
}]
}`)
s.NoError(err)
s.Contains(result, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
}