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:
parent
7433828a26
commit
1fb7d47c66
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"]`,
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
// }
|
|
@ -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, "")
|
||||
|
|
|
@ -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 ""
|
||||
// }
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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, ðConf)
|
||||
if err == nil {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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`,
|
||||
`{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) + `,
|
||||
|
|
Loading…
Reference in New Issue