JSON RPC Proxy configuration (#193)

IMPORTANT: The known issue is that in case of configured UpstreamRPCServer transactions are sent immediately not waiting for CompleteTransaction or DiscardTransaction which brings inconsistency in behaviour and acts as a security breach.

* new UpstreamRPConfig in geth/params to provide upstream configuration
* conditional start of ethereum blockchain sequence based on NodeConfig.UpstreamConfig.Enabled flag state
This commit is contained in:
Ewetumo Alexander 2017-08-15 11:27:12 +01:00 committed by Ivan Tomilov
parent 7433828a26
commit 1fb7d47c66
24 changed files with 1307 additions and 296 deletions

View File

@ -29,12 +29,13 @@ func NewStatusBackend() *StatusBackend {
nodeManager := node.NewNodeManager()
accountManager := node.NewAccountManager(nodeManager)
return &StatusBackend{
nodeManager: nodeManager,
accountManager: accountManager,
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
jailManager: jail.New(nodeManager),
jailManager: jail.New(nodeManager, accountManager),
rpcManager: node.NewRPCManager(nodeManager),
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
}
}

View File

@ -128,7 +128,7 @@ func (s *BackendTestSuite) TestJailSendQueuedTransaction() {
{
`["commands", "send"]`,
txParams,
`{"result": {"context":{"` + jail.SendTransactionRequest + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
`{"result": {"context":{"` + params.SendTransactionMethodName + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
},
{
`["commands", "getBalance"]`,

135
geth/common/rpccall.go Normal file
View File

@ -0,0 +1,135 @@
package common
import (
"errors"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// RPCCall represents a unit of a rpc request which is to be executed.
type RPCCall struct {
ID int64
Method string
Params []interface{}
}
// contains series of errors for parsing operations.
var (
ErrInvalidFromAddress = errors.New("Failed to parse From Address")
ErrInvalidToAddress = errors.New("Failed to parse To Address")
)
// ParseFromAddress returns the address associated with the RPCCall.
func (r RPCCall) ParseFromAddress() (gethcommon.Address, error) {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return gethcommon.HexToAddress("0x"), ErrInvalidFromAddress
}
from, ok := params["from"].(string)
if !ok {
return gethcommon.HexToAddress("0x"), ErrInvalidFromAddress
}
return gethcommon.HexToAddress(from), nil
}
// ParseToAddress returns the gethcommon.Address associated with the call.
func (r RPCCall) ParseToAddress() (gethcommon.Address, error) {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return gethcommon.HexToAddress("0x"), ErrInvalidToAddress
}
to, ok := params["to"].(string)
if !ok {
return gethcommon.HexToAddress("0x"), ErrInvalidToAddress
}
return gethcommon.HexToAddress(to), nil
}
// ParseData returns the bytes associated with the call.
func (r RPCCall) ParseData() hexutil.Bytes {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return hexutil.Bytes("0x")
}
data, ok := params["data"].(string)
if !ok {
data = "0x"
}
byteCode, err := hexutil.Decode(data)
if err != nil {
byteCode = hexutil.Bytes(data)
}
return byteCode
}
// ParseValue returns the hex big associated with the call.
// nolint: dupl
func (r RPCCall) ParseValue() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
//return (*hexutil.Big)(big.NewInt("0x0"))
}
inputValue, ok := params["value"].(string)
if !ok {
return nil
}
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
return (*hexutil.Big)(parsedValue)
}
// ParseGas returns the hex big associated with the call.
// nolint: dupl
func (r RPCCall) ParseGas() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
}
inputValue, ok := params["gas"].(string)
if !ok {
return nil
}
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
return (*hexutil.Big)(parsedValue)
}
// ParseGasPrice returns the hex big associated with the call.
// nolint: dupl
func (r RPCCall) ParseGasPrice() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
}
inputValue, ok := params["gasPrice"].(string)
if !ok {
return nil
}
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
return (*hexutil.Big)(parsedValue)
}

View File

@ -265,6 +265,16 @@ type AccountInfo struct {
Error string `json:"error"`
}
// StopRPCCallError defines a error type specific for killing a execution process.
type StopRPCCallError struct {
Err error
}
// Error returns the internal error associated with the critical error.
func (c StopRPCCallError) Error() string {
return c.Err.Error()
}
// CompleteTransactionResult is a JSON returned from transaction complete function (used in exposed method)
type CompleteTransactionResult struct {
ID string `json:"id"`

View File

@ -0,0 +1,209 @@
package jail
import (
"context"
"encoding/json"
"math/big"
"time"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
)
// ExecutionPolicy provides a central container for the executions of RPCCall requests for both
// remote/upstream processing and internal node processing.
type ExecutionPolicy struct {
nodeManager common.NodeManager
accountManager common.AccountManager
}
// NewExecutionPolicy returns a new instance of ExecutionPolicy.
func NewExecutionPolicy(nodeManager common.NodeManager, accountManager common.AccountManager) *ExecutionPolicy {
return &ExecutionPolicy{
nodeManager: nodeManager,
accountManager: accountManager,
}
}
// ExecuteSendTransaction defines a function to execute RPC requests for eth_sendTransaction method only.
func (ep *ExecutionPolicy) ExecuteSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
config, err := ep.nodeManager.NodeConfig()
if err != nil {
return nil, err
}
if config.UpstreamConfig.Enabled {
return ep.executeRemoteSendTransaction(req, call)
}
return ep.executeLocalSendTransaction(req, call)
}
// executeRemoteSendTransaction defines a function to execute RPC method eth_sendTransaction over the upstream server.
func (ep *ExecutionPolicy) executeRemoteSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
config, err := ep.nodeManager.NodeConfig()
if err != nil {
return nil, err
}
selectedAcct, err := ep.accountManager.SelectedAccount()
if err != nil {
return nil, err
}
client, err := ep.nodeManager.RPCClient()
if err != nil {
return nil, err
}
fromAddr, err := req.ParseFromAddress()
if err != nil {
return nil, err
}
toAddr, err := req.ParseToAddress()
if err != nil {
return nil, err
}
// We need to request a new transaction nounce from upstream node.
ctx, canceller := context.WithTimeout(context.Background(), time.Minute)
defer canceller()
var num hexutil.Uint
if err := client.CallContext(ctx, &num, "eth_getTransactionCount", fromAddr, "pending"); err != nil {
return nil, err
}
nonce := uint64(num)
gas := (*big.Int)(req.ParseGas())
dataVal := []byte(req.ParseData())
priceVal := (*big.Int)(req.ParseValue())
gasPrice := (*big.Int)(req.ParseGasPrice())
chainID := big.NewInt(int64(config.NetworkID))
tx := types.NewTransaction(nonce, toAddr, priceVal, gas, gasPrice, dataVal)
txs, err := types.SignTx(tx, types.NewEIP155Signer(chainID), selectedAcct.AccountKey.PrivateKey)
if err != nil {
return nil, err
}
// Attempt to get the hex version of the transaction.
txBytes, err := rlp.EncodeToBytes(txs)
if err != nil {
return nil, err
}
//TODO(influx6): Should we use a single context with a higher timeout, say 3-5 minutes
// for calls to rpcClient?
ctx2, canceler2 := context.WithTimeout(context.Background(), time.Minute)
defer canceler2()
var result json.RawMessage
if err := client.CallContext(ctx2, &result, "eth_sendRawTransaction", gethcommon.ToHex(txBytes)); err != nil {
return nil, err
}
resp, err := call.Otto.Object(`({"jsonrpc":"2.0"})`)
if err != nil {
return nil, err
}
resp.Set("id", req.ID)
resp.Set("result", result)
resp.Set("hash", txs.Hash().String())
return resp, nil
}
// executeLocalSendTransaction defines a function which handles execution of RPC method over the internal rpc server
// from the eth.LightClient. It specifically caters to process eth_sendTransaction.
func (ep *ExecutionPolicy) executeLocalSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
resp, err := call.Otto.Object(`({"jsonrpc":"2.0"})`)
if err != nil {
return nil, err
}
resp.Set("id", req.ID)
txHash, err := processRPCCall(ep.nodeManager, req, call)
resp.Set("result", txHash.Hex())
if err != nil {
resp = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
return resp, nil
}
return resp, nil
}
// ExecuteOtherTransaction defines a function which handles the processing of non `eth_sendTransaction`
// rpc request to the internal node server.
func (ep *ExecutionPolicy) ExecuteOtherTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
client, err := ep.nodeManager.RPCClient()
if err != nil {
return nil, common.StopRPCCallError{Err: err}
}
JSON, err := call.Otto.Object("JSON")
if err != nil {
return nil, err
}
var result json.RawMessage
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("id", req.ID)
// do extra request pre processing (persist message id)
// within function semaphore will be acquired and released,
// so that no more than one client (per cell) can enter
messageID, err := preProcessRequest(call.Otto, req)
if err != nil {
return nil, common.StopRPCCallError{Err: err}
}
err = client.Call(&result, req.Method, req.Params...)
switch err := err.(type) {
case nil:
if result == nil {
// Special case null because it is decoded as an empty
// raw message for some reason.
resp.Set("result", otto.NullValue())
} else {
resultVal, callErr := JSON.Call("parse", string(result))
if callErr != nil {
resp = newErrorResponse(call.Otto, -32603, callErr.Error(), &req.ID).Object()
} else {
resp.Set("result", resultVal)
}
}
case rpc.Error:
resp.Set("error", map[string]interface{}{
"code": err.ErrorCode(),
"message": err.Error(),
})
default:
resp = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
}
// do extra request post processing (setting back tx context)
postProcessRequest(call.Otto, req, messageID)
return resp, nil
}

View File

@ -97,7 +97,7 @@ func makeSendHandler(jail *Jail) func(call otto.FunctionCall) (response otto.Val
// makeJethIsConnectedHandler returns jeth.isConnected() handler
func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (response otto.Value) {
return func(call otto.FunctionCall) otto.Value {
client, err := jail.requestManager.RPCClient()
client, err := jail.nodeManager.RPCClient()
if err != nil {
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
}

View File

@ -1,15 +1,19 @@
package jail
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/rpc"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/log"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/static"
"fknsrs.biz/p/ottoext/loop"
@ -28,16 +32,21 @@ var (
type Jail struct {
// FIXME(tiabc): This mutex handles cells field access and must be renamed appropriately: cellsMutex
sync.RWMutex
requestManager *RequestManager
nodeManager common.NodeManager
accountManager common.AccountManager
policy *ExecutionPolicy
cells map[string]*JailCell // jail supports running many isolated instances of jailed runtime
baseJSCode string // JavaScript used to initialize all new cells with
}
// New returns new Jail environment.
func New(nodeManager common.NodeManager) *Jail {
// New returns new Jail environment with the associated NodeManager and
// AccountManager.
func New(nodeManager common.NodeManager, accountManager common.AccountManager) *Jail {
return &Jail{
nodeManager: nodeManager,
accountManager: accountManager,
cells: make(map[string]*JailCell),
requestManager: NewRequestManager(nodeManager),
policy: NewExecutionPolicy(nodeManager, accountManager),
}
}
@ -167,91 +176,52 @@ func (jail *Jail) Call(chatID string, path string, args string) string {
// Send will serialize the first argument, send it to the node and returns the response.
// nolint: errcheck, unparam
func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
client, err := jail.requestManager.RPCClient()
if err != nil {
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
}
// Remarshal the request into a Go value.
JSON, _ := call.Otto.Object("JSON")
reqVal, err := JSON.Call("stringify", call.Argument(0))
if err != nil {
throwJSException(err.Error())
}
var (
rawReq = []byte(reqVal.String())
reqs []RPCCall
reqs []common.RPCCall
batch bool
)
if rawReq[0] == '[' {
batch = true
json.Unmarshal(rawReq, &reqs)
} else {
batch = false
reqs = make([]RPCCall, 1)
reqs = make([]common.RPCCall, 1)
json.Unmarshal(rawReq, &reqs[0])
}
// Execute the requests.
resps, _ := call.Otto.Object("new Array()")
// Execute the requests.
for _, req := range reqs {
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("id", req.ID)
var result json.RawMessage
var resErr error
var res *otto.Object
// execute directly w/o RPC call to node
if req.Method == SendTransactionRequest {
txHash, err := jail.requestManager.ProcessSendTransactionRequest(call.Otto, req)
resp.Set("result", txHash.Hex())
if err != nil {
resp = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
}
resps.Call("push", resp)
continue
}
// do extra request pre processing (persist message id)
// within function semaphore will be acquired and released,
// so that no more than one client (per cell) can enter
messageID, err := jail.requestManager.PreProcessRequest(call.Otto, req)
if err != nil {
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
}
errc := make(chan error, 1)
errc2 := make(chan error)
go func() {
errc2 <- <-errc
}()
errc <- client.Call(&result, req.Method, req.Params...)
err = <-errc2
switch err := err.(type) {
case nil:
if result == nil {
// Special case null because it is decoded as an empty
// raw message for some reason.
resp.Set("result", otto.NullValue())
} else {
resultVal, callErr := JSON.Call("parse", string(result))
if callErr != nil {
resp = newErrorResponse(call.Otto, -32603, callErr.Error(), &req.ID).Object()
} else {
resp.Set("result", resultVal)
}
}
case rpc.Error:
resp.Set("error", map[string]interface{}{
"code": err.ErrorCode(),
"message": err.Error(),
})
switch req.Method {
case params.SendTransactionMethodName:
res, resErr = jail.policy.ExecuteSendTransaction(req, call)
default:
resp = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
res, resErr = jail.policy.ExecuteOtherTransaction(req, call)
}
resps.Call("push", resp)
// do extra request post processing (setting back tx context)
jail.requestManager.PostProcessRequest(call.Otto, req, messageID)
if resErr != nil {
switch resErr.(type) {
case common.StopRPCCallError:
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
default:
res = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
}
}
resps.Call("push", res)
}
// Return the responses either to the callback (if supplied)
@ -261,18 +231,115 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
} else {
response, _ = resps.Get("0")
}
if fn := call.Argument(1); fn.Class() == "Function" {
fn.Call(otto.NullValue(), otto.NullValue(), response)
return otto.UndefinedValue()
}
return response
}
func newErrorResponse(otto *otto.Otto, code int, msg string, id interface{}) otto.Value {
//==================================================================================================================================
func processRPCCall(manager common.NodeManager, req common.RPCCall, call otto.FunctionCall) (gethcommon.Hash, error) {
lightEthereum, err := manager.LightEthereumService()
if err != nil {
return gethcommon.Hash{}, err
}
backend := lightEthereum.StatusBackend
messageID, err := preProcessRequest(call.Otto, req)
if err != nil {
return gethcommon.Hash{}, err
}
// onSendTransactionRequest() will use context to obtain and release ticket
ctx := context.Background()
ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
// this call blocks, up until Complete Transaction is called
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
if err != nil {
return gethcommon.Hash{}, err
}
// invoke post processing
postProcessRequest(call.Otto, req, messageID)
return txHash, nil
}
// preProcessRequest pre-processes a given RPC call to a given Otto VM
func preProcessRequest(vm *otto.Otto, req common.RPCCall) (string, error) {
messageID := currentMessageID(vm.Context())
return messageID, nil
}
// postProcessRequest post-processes a given RPC call to a given Otto VM
func postProcessRequest(vm *otto.Otto, req common.RPCCall, messageID string) {
if len(messageID) > 0 {
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
}
// set extra markers for queued transaction requests
if req.Method == params.SendTransactionMethodName {
vm.Call("addContext", nil, messageID, params.SendTransactionMethodName, true) // nolint: errcheck
}
}
func sendTxArgsFromRPCCall(req common.RPCCall) status.SendTxArgs {
if req.Method != params.SendTransactionMethodName { // no need to persist extra state for other requests
return status.SendTxArgs{}
}
var err error
var fromAddr, toAddr gethcommon.Address
fromAddr, err = req.ParseFromAddress()
if err != nil {
fromAddr = gethcommon.HexToAddress("0x0")
}
toAddr, err = req.ParseToAddress()
if err != nil {
toAddr = gethcommon.HexToAddress("0x0")
}
return status.SendTxArgs{
To: &toAddr,
From: fromAddr,
Value: req.ParseValue(),
Data: req.ParseData(),
Gas: req.ParseGas(),
GasPrice: req.ParseGasPrice(),
}
}
// currentMessageID looks for `status.message_id` variable in current JS context
func currentMessageID(ctx otto.Context) string {
if statusObj, ok := ctx.Symbols["status"]; ok {
messageID, err := statusObj.Object().Get("message_id")
if err != nil {
return ""
}
if messageID, err := messageID.ToString(); err == nil {
return messageID
}
}
return ""
}
//==========================================================================================================
func newErrorResponse(vm *otto.Otto, code int, msg string, id interface{}) otto.Value {
// Bundle the error into a JSON RPC call response
m := map[string]interface{}{"jsonrpc": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
res, _ := json.Marshal(m)
val, _ := otto.Run("(" + string(res) + ")")
val, _ := vm.Run("(" + string(res) + ")")
return val
}

View File

@ -92,7 +92,7 @@ func (s *JailTestSuite) TestJailLoopInCall() {
require := s.Require()
require.NotNil(s.jail)
s.StartTestNode(params.RopstenNetworkID)
s.StartTestNode(params.RopstenNetworkID, true)
defer s.StopTestNode()
// load Status JS and add test command to it

357
geth/jail/jail_rpc_test.go Normal file
View File

@ -0,0 +1,357 @@
package jail_test
import (
"testing"
"time"
"encoding/json"
"net/http"
"net/http/httptest"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing"
"github.com/stretchr/testify/suite"
)
type txRequest struct {
Method string `json:"method"`
Version string `json:"jsonrpc"`
ID int `json:"id,omitempty"`
Payload json.RawMessage `json:"params,omitempty"`
}
type service struct {
Handler http.HandlerFunc
}
func (s service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Handler(w, r)
}
//==================================================================================================
func TestJailRPCTestSuite(t *testing.T) {
suite.Run(t, new(JailRPCTestSuite))
}
type JailRPCTestSuite struct {
BaseTestSuite
Account common.AccountManager
Policy *jail.ExecutionPolicy
}
func (s *JailRPCTestSuite) SetupTest() {
require := s.Require()
nodeman := node.NewNodeManager()
require.NotNil(nodeman)
acctman := node.NewAccountManager(nodeman)
require.NotNil(acctman)
policy := jail.NewExecutionPolicy(nodeman, acctman)
require.NotNil(policy)
s.Policy = policy
s.Account = acctman
s.NodeManager = nodeman
}
func (s *JailRPCTestSuite) TestSendTransaction() {
require := s.Require()
require.NotNil(s.NodeManager)
odFunc := otto.FunctionCall{
Otto: otto.New(),
This: otto.NullValue(),
}
request := common.RPCCall{
ID: 65454545334343,
Method: "eth_sendTransaction",
Params: []interface{}{
map[string]interface{}{
"from": TestConfig.Account1.Address,
"to": "0xe410006cad020e3690c8ba21ed8b0f065dde2453",
"value": "0x2",
"nonce": "0x1",
"data": "Will-power",
"gasPrice": "0x4a817c800",
"gasLimit": "0x5208",
"chainId": 3391,
},
},
}
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
rpcService := new(service)
rpcService.Handler = func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var txReq txRequest
if err := json.NewDecoder(r.Body).Decode(&txReq); err != nil {
require.NoError(err)
return
}
switch txReq.Method {
case "eth_getTransactionCount":
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"jsonrpc": "2.0", "status":200, "result": "0x434"}`))
return
}
payload := ([]byte)(txReq.Payload)
var bu []interface{}
jserr := json.Unmarshal(payload, &bu)
require.NoError(jserr)
require.NotNil(bu)
require.Len(bu, 1)
buElem, ok := bu[0].(string)
require.Equal(ok, true)
decoded, err := hexutil.Decode(buElem)
require.NoError(err)
require.NotNil(decoded)
var tx types.Transaction
decodeErr := rlp.DecodeBytes(decoded, &tx)
require.NoError(decodeErr)
// Validate we are receiving transaction from the proper network chain.
require.Equal(tx.ChainId().Int64(), int64(nodeConfig.NetworkID))
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"jsonrpc": "2.0", "status":200, "result": "3434=done"}`))
}
httpRPCServer := httptest.NewServer(rpcService)
nodeConfig.UpstreamConfig.URL = httpRPCServer.URL
// Start NodeManagers Node
started, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err)
select {
case <-started:
break
case <-time.After(1 * time.Second):
require.Fail("Failed to start NodeManager")
break
}
defer s.NodeManager.StopNode()
client, err := s.NodeManager.RPCClient()
require.NoError(err)
require.NotNil(client)
selectErr := s.Account.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(selectErr)
res, err := s.Policy.ExecuteSendTransaction(request, odFunc)
require.NoError(err)
result, err := res.Get("result")
require.NoError(err)
require.NotNil(result)
exported, err := result.Export()
require.NoError(err)
rawJSON, ok := exported.(json.RawMessage)
require.True(ok, "Expected raw json payload")
require.Equal(string(rawJSON), "\"3434=done\"")
}
// func (s *JailRPCTestSuite) TestMainnetAcceptance() {
// require := s.Require()
// require.NotNil(s.NodeManager)
// odFunc := otto.FunctionCall{
// Otto: otto.New(),
// This: otto.NullValue(),
// }
// request := common.RPCCall{
// ID: 65454545334343,
// Method: "eth_sendTransaction",
// Params: []interface{}{
// map[string]interface{}{
// "from": TestConfig.Account1.Address,
// "to": "0xe410006cad020e3690c8ba21ed8b0f065dde2453",
// "value": "0x2",
// "nonce": "0x1",
// "data": "Will-power",
// "gasPrice": "0x4a817c800",
// "gasLimit": "0x5208",
// "chainId": 3391,
// },
// },
// }
// nodeConfig, err := MakeTestNodeConfig(params.MainNetworkID)
// require.NoError(err)
// nodeConfig.UpstreamConfig.Enabled = true
// // Start NodeManagers Node
// started, err := s.NodeManager.StartNode(nodeConfig)
// require.NoError(err)
// select {
// case <-started:
// break
// case <-time.After(1 * time.Second):
// require.Fail("Failed to start NodeManager")
// break
// }
// defer s.NodeManager.StopNode()
// client, err := s.NodeManager.RPCClient()
// require.NoError(err)
// require.NotNil(client)
// selectErr := s.Account.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
// require.NoError(selectErr)
// res, err := s.Policy.ExecuteSendTransaction(request, odFunc)
// require.NoError(err)
// _, err = res.Get("hash")
// require.NoError(err)
// }
// func (s *JailRPCTestSuite) TestRobstenAcceptance() {
// require := s.Require()
// require.NotNil(s.NodeManager)
// odFunc := otto.FunctionCall{
// Otto: otto.New(),
// This: otto.NullValue(),
// }
// request := common.RPCCall{
// ID: 65454545334343,
// Method: "eth_sendTransaction",
// Params: []interface{}{
// map[string]interface{}{
// "from": TestConfig.Account1.Address,
// "to": "0xe410006cad020e3690c8ba21ed8b0f065dde2453",
// "value": "0x2",
// "nonce": "0x1",
// "data": "Will-power",
// "gasPrice": "0x4a817c800",
// "gasLimit": "0x5208",
// "chainId": 3391,
// },
// },
// }
// nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
// require.NoError(err)
// nodeConfig.UpstreamConfig.Enabled = true
// // Start NodeManagers Node
// started, err := s.NodeManager.StartNode(nodeConfig)
// require.NoError(err)
// select {
// case <-started:
// break
// case <-time.After(1 * time.Second):
// require.Fail("Failed to start NodeManager")
// break
// }
// defer s.NodeManager.StopNode()
// client, err := s.NodeManager.RPCClient()
// require.NoError(err)
// require.NotNil(client)
// selectErr := s.Account.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
// require.NoError(selectErr)
// res, err := s.Policy.ExecuteSendTransaction(request, odFunc)
// require.NoError(err)
// _, err = res.Get("hash")
// require.NoError(err)
// }
// func (s *JailRPCTestSuite) TestRinkebyAcceptance() {
// require := s.Require()
// require.NotNil(s.NodeManager)
// odFunc := otto.FunctionCall{
// Otto: otto.New(),
// This: otto.NullValue(),
// }
// request := common.RPCCall{
// ID: 65454545334343,
// Method: "eth_sendTransaction",
// Params: []interface{}{
// map[string]interface{}{
// "from": TestConfig.Account1.Address,
// "to": "0xe410006cad020e3690c8ba21ed8b0f065dde2453",
// "value": "0x2",
// "nonce": "0x1",
// "data": "Will-power",
// "gasPrice": "0x4a817c800",
// "gasLimit": "0x5208",
// "chainId": 3391,
// },
// },
// }
// nodeConfig, err := MakeTestNodeConfig(params.RinkebyNetworkID)
// require.NoError(err)
// nodeConfig.UpstreamConfig.Enabled = true
// // Start NodeManagers Node
// started, err := s.NodeManager.StartNode(nodeConfig)
// require.NoError(err)
// select {
// case <-started:
// break
// case <-time.After(1 * time.Second):
// require.Fail("Failed to start NodeManager")
// break
// }
// defer s.NodeManager.StopNode()
// client, err := s.NodeManager.RPCClient()
// require.NoError(err)
// require.NotNil(client)
// selectErr := s.Account.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
// require.NoError(selectErr)
// res, err := s.Policy.ExecuteSendTransaction(request, odFunc)
// require.NoError(err)
// _, err = res.Get("hash")
// require.NoError(err)
// }

View File

@ -30,12 +30,19 @@ type JailTestSuite struct {
}
func (s *JailTestSuite) SetupTest() {
s.NodeManager = node.NewNodeManager()
s.Require().NotNil(s.NodeManager)
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
s.jail = jail.New(s.NodeManager)
s.Require().NotNil(s.jail)
s.Require().IsType(&jail.Jail{}, s.jail)
require := s.Require()
nodeManager := node.NewNodeManager()
require.NotNil(nodeManager)
accountManager := node.NewAccountManager(nodeManager)
require.NotNil(accountManager)
jail := jail.New(nodeManager, accountManager)
require.NotNil(jail)
s.jail = jail
s.NodeManager = nodeManager
}
func (s *JailTestSuite) TestInit() {
@ -119,7 +126,7 @@ func (s *JailTestSuite) TestJailRPCSend() {
require := s.Require()
require.NotNil(s.jail)
s.StartTestNode(params.RopstenNetworkID)
s.StartTestNode(params.RopstenNetworkID, false)
defer s.StopTestNode()
// load Status JS and add test command to it
@ -154,7 +161,8 @@ func (s *JailTestSuite) TestIsConnected() {
require.NotNil(s.jail)
// TODO(tiabc): Is this required?
s.StartTestNode(params.RopstenNetworkID)
s.StartTestNode(params.RopstenNetworkID, false)
defer s.StopTestNode()
s.jail.Parse(testChatID, "")

View File

@ -1,235 +1,234 @@
package jail
import (
"context"
// import (
// "context"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/rpc"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
)
// gethcommon "github.com/ethereum/go-ethereum/common"
// "github.com/ethereum/go-ethereum/common/hexutil"
// "github.com/ethereum/go-ethereum/les/status"
// "github.com/ethereum/go-ethereum/rpc"
// "github.com/robertkrimen/otto"
// "github.com/status-im/status-go/geth/common"
// )
const (
// SendTransactionRequest is triggered on send transaction request
SendTransactionRequest = "eth_sendTransaction"
)
// const (
// // SendTransactionRequest is triggered on send transaction request
// SendTransactionRequest = "eth_sendTransaction"
// )
// RequestManager represents interface to manage jailed requests.
// Whenever some request passed to a Jail, needs to be pre/post processed,
// request manager is the right place for that.
type RequestManager struct {
nodeManager common.NodeManager
}
// // RequestManager represents interface to manage jailed requests.
// // Whenever some request passed to a Jail, needs to be pre/post processed,
// // request manager is the right place for that.
// type RequestManager struct {
// nodeManager common.NodeManager
// }
// NewRequestManager returns a new instance of the RequestManager pointer.
func NewRequestManager(nodeManager common.NodeManager) *RequestManager {
return &RequestManager{
nodeManager: nodeManager,
}
}
// func NewRequestManager(nodeManager common.NodeManager) *RequestManager {
// return &RequestManager{
// nodeManager: nodeManager,
// }
// }
// PreProcessRequest pre-processes a given RPC call to a given Otto VM
func (m *RequestManager) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
messageID := currentMessageID(vm.Context())
// // PreProcessRequest pre-processes a given RPC call to a given Otto VM
// func (m *RequestManager) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
// messageID := currentMessageID(vm.Context())
return messageID, nil
}
// return messageID, nil
// }
// PostProcessRequest post-processes a given RPC call to a given Otto VM
func (m *RequestManager) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
// Errors are ignored because addContext may not exist and it's alright.
if len(messageID) > 0 {
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
}
// // PostProcessRequest post-processes a given RPC call to a given Otto VM
// func (m *RequestManager) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
// if len(messageID) > 0 {
// vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
// }
// set extra markers for queued transaction requests
if req.Method == SendTransactionRequest {
vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
}
}
// // set extra markers for queued transaction requests
// if req.Method == SendTransactionRequest {
// vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
// }
// }
// ProcessSendTransactionRequest processes send transaction request.
// Both pre and post processing happens within this function. Pre-processing
// happens before transaction is send to backend, and post processing occurs
// when backend notifies that transaction sending is complete (either successfully
// or with error)
func (m *RequestManager) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (gethcommon.Hash, error) {
lightEthereum, err := m.nodeManager.LightEthereumService()
if err != nil {
return gethcommon.Hash{}, err
}
// // ProcessSendTransactionRequest processes send transaction request.
// // Both pre and post processing happens within this function. Pre-processing
// // happens before transaction is send to backend, and post processing occurs
// // when backend notifies that transaction sending is complete (either successfully
// // or with error)
// func (m *RequestManager) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (gethcommon.Hash, error) {
// lightEthereum, err := m.nodeManager.LightEthereumService()
// if err != nil {
// return gethcommon.Hash{}, err
// }
backend := lightEthereum.StatusBackend
// backend := lightEthereum.StatusBackend
messageID, err := m.PreProcessRequest(vm, req)
if err != nil {
return gethcommon.Hash{}, err
}
// onSendTransactionRequest() will use context to obtain and release ticket
ctx := context.Background()
ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
// messageID, err := m.PreProcessRequest(vm, req)
// if err != nil {
// return gethcommon.Hash{}, err
// }
// this call blocks, up until Complete Transaction is called
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
if err != nil {
return gethcommon.Hash{}, err
}
// // onSendTransactionRequest() will use context to obtain and release ticket
// ctx := context.Background()
// ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
// invoke post processing
m.PostProcessRequest(vm, req, messageID)
// // this call blocks, up until Complete Transaction is called
// txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
// if err != nil {
// return gethcommon.Hash{}, err
// }
return txHash, nil
}
// // invoke post processing
// m.PostProcessRequest(vm, req, messageID)
// RPCClient returns RPC client instance, creating it if necessary.
func (m *RequestManager) RPCClient() (*rpc.Client, error) {
return m.nodeManager.RPCClient()
}
// return txHash, nil
// }
// RPCCall represents RPC call parameters
type RPCCall struct {
ID int64
Method string
Params []interface{}
}
// // RPCClient returns RPC client instance, creating it if necessary.
// func (m *RequestManager) RPCClient() (*rpc.Client, error) {
// return m.nodeManager.RPCClient()
// }
func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
return status.SendTxArgs{}
}
// // RPCCall represents RPC call parameters
// type RPCCall struct {
// ID int64
// Method string
// Params []interface{}
// }
return status.SendTxArgs{
From: req.parseFromAddress(),
To: req.parseToAddress(),
Value: req.parseValue(),
Data: req.parseData(),
Gas: req.parseGas(),
GasPrice: req.parseGasPrice(),
}
}
// func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
// if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
// return status.SendTxArgs{}
// }
func (r RPCCall) parseFromAddress() gethcommon.Address {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return gethcommon.HexToAddress("0x")
}
// return status.SendTxArgs{
// From: req.parseFromAddress(),
// To: req.parseToAddress(),
// Value: req.parseValue(),
// Data: req.parseData(),
// Gas: req.parseGas(),
// GasPrice: req.parseGasPrice(),
// }
// }
from, ok := params["from"].(string)
if !ok {
from = "0x"
}
// func (r RPCCall) parseFromAddress() gethcommon.Address {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return gethcommon.HexToAddress("0x")
// }
return gethcommon.HexToAddress(from)
}
// from, ok := params["from"].(string)
// if !ok {
// from = "0x"
// }
func (r RPCCall) parseToAddress() *gethcommon.Address {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
}
// return gethcommon.HexToAddress(from)
// }
to, ok := params["to"].(string)
if !ok {
return nil
}
// func (r RPCCall) parseToAddress() *gethcommon.Address {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return nil
// }
address := gethcommon.HexToAddress(to)
return &address
}
// to, ok := params["to"].(string)
// if !ok {
// return nil
// }
func (r RPCCall) parseData() hexutil.Bytes {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return hexutil.Bytes("0x")
}
// address := gethcommon.HexToAddress(to)
// return &address
// }
data, ok := params["data"].(string)
if !ok {
data = "0x"
}
// func (r RPCCall) parseData() hexutil.Bytes {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return hexutil.Bytes("0x")
// }
byteCode, err := hexutil.Decode(data)
if err != nil {
byteCode = hexutil.Bytes(data)
}
// data, ok := params["data"].(string)
// if !ok {
// data = "0x"
// }
return byteCode
}
// byteCode, err := hexutil.Decode(data)
// if err != nil {
// byteCode = hexutil.Bytes(data)
// }
// nolint: dupl
func (r RPCCall) parseValue() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
//return (*hexutil.Big)(big.NewInt("0x0"))
}
// return byteCode
// }
inputValue, ok := params["value"].(string)
if !ok {
return nil
}
// // nolint: dupl
// func (r RPCCall) parseValue() *hexutil.Big {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return nil
// //return (*hexutil.Big)(big.NewInt("0x0"))
// }
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
// inputValue, ok := params["value"].(string)
// if !ok {
// return nil
// }
return (*hexutil.Big)(parsedValue)
}
// parsedValue, err := hexutil.DecodeBig(inputValue)
// if err != nil {
// return nil
// }
// nolint: dupl
func (r RPCCall) parseGas() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
}
// return (*hexutil.Big)(parsedValue)
// }
inputValue, ok := params["gas"].(string)
if !ok {
return nil
}
// // nolint: dupl
// func (r RPCCall) parseGas() *hexutil.Big {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return nil
// }
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
// inputValue, ok := params["gas"].(string)
// if !ok {
// return nil
// }
return (*hexutil.Big)(parsedValue)
}
// parsedValue, err := hexutil.DecodeBig(inputValue)
// if err != nil {
// return nil
// }
// nolint: dupl
func (r RPCCall) parseGasPrice() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
return nil
}
// return (*hexutil.Big)(parsedValue)
// }
inputValue, ok := params["gasPrice"].(string)
if !ok {
return nil
}
// // nolint: dupl
// func (r RPCCall) parseGasPrice() *hexutil.Big {
// params, ok := r.Params[0].(map[string]interface{})
// if !ok {
// return nil
// }
parsedValue, err := hexutil.DecodeBig(inputValue)
if err != nil {
return nil
}
// inputValue, ok := params["gasPrice"].(string)
// if !ok {
// return nil
// }
return (*hexutil.Big)(parsedValue)
}
// parsedValue, err := hexutil.DecodeBig(inputValue)
// if err != nil {
// return nil
// }
// currentMessageID looks for `status.message_id` variable in current JS context
func currentMessageID(ctx otto.Context) string {
if statusObj, ok := ctx.Symbols["status"]; ok {
messageID, err := statusObj.Object().Get("message_id")
if err != nil {
return ""
}
if messageID, err := messageID.ToString(); err == nil {
return messageID
}
}
// return (*hexutil.Big)(parsedValue)
// }
return ""
}
// // currentMessageID looks for `status.message_id` variable in current JS context
// func currentMessageID(ctx otto.Context) string {
// if statusObj, ok := ctx.Symbols["status"]; ok {
// messageID, err := statusObj.Object().Get("message_id")
// if err != nil {
// return ""
// }
// if messageID, err := messageID.ToString(); err == nil {
// return messageID
// }
// }
// return ""
// }

View File

@ -502,12 +502,17 @@ func (m *NodeManager) AccountKeyStore() (*keystore.KeyStore, error) {
return keyStore, nil
}
// RPCClient exposes reference to RPC client connected to the running node
// RPCClient exposes reference to RPC client connected to the running node.
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
if m == nil {
return nil, ErrInvalidNodeManager
}
config, err := m.NodeConfig()
if err != nil {
return nil, err
}
m.RLock()
defer m.RUnlock()
@ -515,8 +520,20 @@ func (m *NodeManager) RPCClient() (*rpc.Client, error) {
if m.node == nil || m.nodeStarted == nil {
return nil, ErrNoRunningNode
}
<-m.nodeStarted
// Connect to upstream RPC server with new client and cache instance.
if config.UpstreamConfig.Enabled {
m.rpcClient, err = rpc.Dial(config.UpstreamConfig.URL)
if err != nil {
log.Error("Failed to conect to upstream RPC server", "error", err)
return nil, err
}
return m.rpcClient, nil
}
if m.rpcClient == nil {
var err error
m.rpcClient, err = m.node.Attach()

View File

@ -248,7 +248,7 @@ func (s *ManagerTestSuite) TestReferences() {
}
// test with node fully started
s.StartTestNode(params.RinkebyNetworkID)
s.StartTestNode(params.RinkebyNetworkID, false)
defer s.StopTestNode()
var nodeReadyTestCases = []struct {
name string
@ -403,7 +403,7 @@ func (s *ManagerTestSuite) TestResetChainData() {
require := s.Require()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID)
s.StartTestNode(params.RinkebyNetworkID, false)
defer s.StopTestNode()
time.Sleep(2 * time.Second) // allow to sync for some time
@ -422,7 +422,7 @@ func (s *ManagerTestSuite) TestRestartNode() {
require := s.Require()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID)
s.StartTestNode(params.RinkebyNetworkID, false)
defer s.StopTestNode()
s.True(s.NodeManager.IsNodeRunning())

View File

@ -70,9 +70,15 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
return nil, ErrNodeMakeFailure
}
// start Ethereum service
if err := activateEthService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err)
// start Ethereum service if we are not expected to use an upstream server.
if !config.UpstreamConfig.Enabled {
if err := activateEthService(stack, config); err != nil {
return nil, fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err)
}
} else {
log.Info("Blockchain synchronization is switched off, RPC requests will be proxied to %s", config.UpstreamConfig.URL)
}
// start Whisper service
@ -163,6 +169,7 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
ethConf.NetworkId = config.NetworkID
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
ethConf.MaxPeers = config.MaxPeers
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
lightEth, err := les.New(ctx, &ethConf)
if err == nil {

View File

@ -1,17 +1,22 @@
package node
import (
"bytes"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"sync"
"time"
"net/url"
"github.com/ethereum/go-ethereum/les/status"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params"
)
const (
@ -60,27 +65,35 @@ func NewRPCManager(nodeManager common.NodeManager) *RPCManager {
// Call executes RPC request on node's in-proc RPC server
func (c *RPCManager) Call(inputJSON string) string {
server, err := c.nodeManager.RPCServer()
config, err := c.nodeManager.NodeConfig()
if err != nil {
return c.makeJSONErrorResponse(err)
}
// allow HTTP requests to block w/o
outputJSON := make(chan string, 1)
go func() {
httpReq := httptest.NewRequest("POST", "/", strings.NewReader(inputJSON))
rr := httptest.NewRecorder()
server.ServeHTTP(rr, httpReq)
// Check the status code is what we expect.
if respStatus := rr.Code; respStatus != http.StatusOK {
log.Error("handler returned wrong status code", "got", respStatus, "want", http.StatusOK)
outputJSON <- c.makeJSONErrorResponse(ErrRPCServerCallFailed)
go func() {
body := bytes.NewBufferString(inputJSON)
var err error
var res []byte
if config.UpstreamConfig.Enabled {
log.Info("Making RPC JSON Request to upstream RPCServer")
res, err = c.callUpstreamStream(config, body)
} else {
log.Info("Making RPC JSON Request to internal RPCServer")
res, err = c.callNodeStream(body)
}
if err != nil {
outputJSON <- c.makeJSONErrorResponse(err)
return
}
// everything is ok, return
outputJSON <- rr.Body.String()
outputJSON <- string(res)
return
}()
// wait till call is complete
@ -94,6 +107,65 @@ func (c *RPCManager) Call(inputJSON string) string {
return c.makeJSONErrorResponse(ErrRPCServerTimeout)
}
// callNodeStream delivers giving request and body content to the external ethereum
// (infura) RPCServer to process the request and returns response.
func (c *RPCManager) callUpstreamStream(config *params.NodeConfig, body io.Reader) ([]byte, error) {
upstreamURL, err := url.Parse(config.UpstreamConfig.URL)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest("POST", upstreamURL.String(), body)
if err != nil {
return nil, err
}
httpClient := http.Client{
Timeout: 20 * time.Second,
}
res, err := httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer res.Body.Close()
if respStatusCode := res.StatusCode; respStatusCode != http.StatusOK {
log.Error("handler returned wrong status code", "got", respStatusCode, "want", http.StatusOK)
return nil, ErrRPCServerCallFailed
}
return ioutil.ReadAll(res.Body)
}
// callNodeStream delivers giving request and body content to the internal ethereum
// RPCServer to process the request.
func (c *RPCManager) callNodeStream(body io.Reader) ([]byte, error) {
server, err := c.nodeManager.RPCServer()
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest("POST", "/", body)
if err != nil {
return nil, err
}
rr := httptest.NewRecorder()
server.ServeHTTP(rr, httpReq)
// Check the status code is what we expect.
if respStatus := rr.Code; respStatus != http.StatusOK {
log.Error("handler returned wrong status code", "got", respStatus, "want", http.StatusOK)
// outputJSON <- c.makeJSONErrorResponse(ErrRPCServerCallFailed)
return nil, ErrRPCServerCallFailed
}
return rr.Body.Bytes(), nil
}
// makeJSONErrorResponse returns error as JSON response
func (c *RPCManager) makeJSONErrorResponse(err error) string {
response := jsonErrResponse{

View File

@ -1,6 +1,7 @@
package node_test
import (
"net/http"
"testing"
"github.com/status-im/status-go/geth/log"
@ -10,6 +11,14 @@ import (
"github.com/stretchr/testify/suite"
)
type service struct {
Handler http.HandlerFunc
}
func (s service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.Handler(w, r)
}
func TestRPCTestSuite(t *testing.T) {
suite.Run(t, new(RPCTestSuite))
}
@ -20,9 +29,11 @@ type RPCTestSuite struct {
func (s *RPCTestSuite) SetupTest() {
require := s.Require()
s.NodeManager = node.NewNodeManager()
require.NotNil(s.NodeManager)
require.IsType(&node.NodeManager{}, s.NodeManager)
nodeManager := node.NewNodeManager()
require.NotNil(nodeManager)
s.NodeManager = nodeManager
}
func (s *RPCTestSuite) TestCallRPC() {
@ -38,10 +49,13 @@ func (s *RPCTestSuite) TestCallRPC() {
nodeConfig.IPCEnabled = false
nodeConfig.WSEnabled = false
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err)
require.NotNil(nodeConfig)
defer s.NodeManager.StopNode()
<-nodeStarted
progress := make(chan struct{}, 25)

View File

@ -30,7 +30,7 @@ func (s *WhisperTestSuite) TestWhisperFilterRace() {
require := s.Require()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID)
s.StartTestNode(params.RinkebyNetworkID, false)
defer s.StopTestNode()
whisperService, err := s.NodeManager.WhisperService()

View File

@ -207,6 +207,20 @@ func (c *BootClusterConfig) String() string {
return string(data)
}
//=====================================================================================
// UpstreamRPCConfig stores configuration for upstream rpc connection.
type UpstreamRPCConfig struct {
// Enabled flag specifies whether feature is enabled
Enabled bool
// URL sets the rpc upstream host address for communication with
// a non-local infura endpoint.
URL string
}
//=====================================================================================
// NodeConfig stores configuration options for a node
type NodeConfig struct {
// DevMode is true when given configuration is to be used during development.
@ -287,6 +301,9 @@ type NodeConfig struct {
// LogToStderr defines whether logged info should also be output to os.Stderr
LogToStderr bool
// UpstreamConfig extra config for providing upstream infura server.
UpstreamConfig UpstreamRPCConfig `json:"UpstreamConfig"`
// BootClusterConfig extra configuration for supporting cluster
BootClusterConfig *BootClusterConfig `json:"BootClusterConfig," validate:"structonly"`
@ -458,9 +475,15 @@ func (c *NodeConfig) updateConfig() error {
if err := c.updateGenesisConfig(); err != nil {
return err
}
if err := c.updateUpstreamConfig(); err != nil {
return err
}
if err := c.updateBootClusterConfig(); err != nil {
return err
}
if err := c.updateRelativeDirsConfig(); err != nil {
return err
}
@ -493,6 +516,27 @@ func (c *NodeConfig) updateGenesisConfig() error {
return nil
}
// updateUpstreamConfig sets the proper UpstreamConfig.URL for the network id being used.
func (c *NodeConfig) updateUpstreamConfig() error {
// If we have a URL already set then keep URL incase
// of custom server.
if c.UpstreamConfig.URL != "" {
return nil
}
switch c.NetworkID {
case MainNetworkID:
c.UpstreamConfig.URL = UpstreamMainNetEthereumNetworkURL
case RopstenNetworkID:
c.UpstreamConfig.URL = UpstreamRopstenEthereumNetworkURL
case RinkebyNetworkID:
c.UpstreamConfig.URL = UpstreamRinkebyEthereumNetworkURL
}
return nil
}
// updateBootClusterConfig loads boot nodes and CHT for a given network and mode.
// This is necessary until we have LES protocol support CHT sync, and better node
// discovery on mobile devices)

View File

@ -72,6 +72,38 @@ var loadConfigTestCases = []struct {
require.Equal(t, "/foo/bar", nodeConfig.KeyStoreDir)
},
},
{
`test Upstream config setting`,
`{
"NetworkId": 3,
"DataDir": "$TMPDIR",
"Name": "TestStatusNode",
"WSPort": 4242,
"IPCEnabled": true,
"WSEnabled": false,
"UpstreamConfig": {
"Enabled": true,
"URL": "http://upstream.loco.net/nodes"
}
}`,
func(t *testing.T, dataDir string, nodeConfig *params.NodeConfig, err error) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if nodeConfig.NetworkID != 3 {
t.Fatal("wrong NetworkId")
}
if !nodeConfig.UpstreamConfig.Enabled {
t.Fatal("wrong UpstreamConfig.Enabled state")
}
if nodeConfig.UpstreamConfig.URL != "http://upstream.loco.net/nodes" {
t.Fatal("wrong UpstreamConfig.URL value")
}
},
},
{
`test parameter overriding`,
`{

View File

@ -29,6 +29,9 @@ const (
// WSHost is a host interface for the websocket RPC server
WSHost = "localhost"
// SendTransactionMethodName defines the name for a giving transaction.
SendTransactionMethodName = "eth_sendTransaction"
// WSPort is a WS-RPC port (replaced in unit tests)
WSPort = 8546
@ -84,6 +87,18 @@ const (
// FirebaseNotificationTriggerURL is URL where FCM notification requests are sent to
FirebaseNotificationTriggerURL = "https://fcm.googleapis.com/fcm/send"
// UpstreamMainNetEthereumNetworkURL is URL where the upstream ethereum network is loaded to
// allow us avoid syncing node.
UpstreamMainNetEthereumNetworkURL = "https://mainnet.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
// UpstreamRopstenEthereumNetworkURL is URL where the upstream ethereum network is loaded to
// allow us avoid syncing node.
UpstreamRopstenEthereumNetworkURL = "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
// UpstreamRinkebyEthereumNetworkURL is URL where the upstream ethereum network is loaded to
// allow us avoid syncing node.
UpstreamRinkebyEthereumNetworkURL = "https://rinkeby.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
// MainNetworkID is id of the main network
MainNetworkID = 1

View File

@ -22,6 +22,10 @@
"LogFile": "geth.log",
"LogLevel": "INFO",
"LogToStderr": true,
"UpstreamConfig": {
"Enabled": false,
"URL": "https://mainnet.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
},
"BootClusterConfig": {
"Enabled": true,
"RootNumber": 805,

View File

@ -22,6 +22,10 @@
"LogFile": "geth.log",
"LogLevel": "INFO",
"LogToStderr": true,
"UpstreamConfig": {
"Enabled": false,
"URL": "https://rinkeby.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
},
"BootClusterConfig": {
"Enabled": true,
"RootNumber": 66,

View File

@ -22,6 +22,10 @@
"LogFile": "geth.log",
"LogLevel": "INFO",
"LogToStderr": true,
"UpstreamConfig": {
"Enabled": false,
"URL": "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
},
"BootClusterConfig": {
"Enabled": true,
"RootNumber": 259,

View File

@ -17,6 +17,7 @@ import (
)
var (
// TestConfig defines the default config usable at package-level.
TestConfig *common.TestConfig
// RootDir is the main application directory
@ -54,18 +55,25 @@ func init() {
}
}
// BaseTestSuite defines a base tests suit which others suites can embedded to
// access initialization methods useful for testing.
type BaseTestSuite struct {
suite.Suite
NodeManager common.NodeManager
}
func (s *BaseTestSuite) StartTestNode(networkID int) {
// StartTestNode initiazes a NodeManager instances with configuration retrieved
// from the test config.
func (s *BaseTestSuite) StartTestNode(networkID int, upstream bool) {
require := s.Require()
require.NotNil(s.NodeManager)
nodeConfig, err := MakeTestNodeConfig(networkID)
require.NoError(err)
nodeConfig.UpstreamConfig.Enabled = upstream
require.Equal(nodeConfig.UpstreamConfig.Enabled, upstream)
keyStoreDir := filepath.Join(TestDataDir, TestNetworkNames[networkID], "keystore")
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
@ -78,6 +86,7 @@ func (s *BaseTestSuite) StartTestNode(networkID int) {
require.True(s.NodeManager.IsNodeRunning())
}
// StopTestNode attempts to stop initialized NodeManager.
func (s *BaseTestSuite) StopTestNode() {
require := s.Require()
require.NotNil(s.NodeManager)
@ -88,6 +97,7 @@ func (s *BaseTestSuite) StopTestNode() {
require.False(s.NodeManager.IsNodeRunning())
}
// FirstBlockHash validates Attach operation for the NodeManager.
func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManager, expectedHash string) {
require.NotNil(nodeManager)
@ -110,6 +120,8 @@ func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManag
require.Equal(expectedHash, firstBlock.Hash.Hex())
}
// MakeTestNodeConfig defines a function to return a giving params.NodeConfig
// where specific network addresses are assigned based on provieded network id.
func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
configJSON := `{
"NetworkId": ` + strconv.Itoa(networkID) + `,