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") 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.

View File

@ -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

View File

@ -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)
} }

View File

@ -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"`

View File

@ -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

View File

@ -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]

View File

@ -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"}`)
} }

View File

@ -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 {

View File

@ -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"}`)
} }