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()
|
nodeManager := node.NewNodeManager()
|
||||||
accountManager := node.NewAccountManager(nodeManager)
|
accountManager := node.NewAccountManager(nodeManager)
|
||||||
|
|
||||||
return &StatusBackend{
|
return &StatusBackend{
|
||||||
nodeManager: nodeManager,
|
nodeManager: nodeManager,
|
||||||
accountManager: accountManager,
|
accountManager: accountManager,
|
||||||
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
|
jailManager: jail.New(nodeManager, accountManager),
|
||||||
jailManager: jail.New(nodeManager),
|
|
||||||
rpcManager: node.NewRPCManager(nodeManager),
|
rpcManager: node.NewRPCManager(nodeManager),
|
||||||
|
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (s *BackendTestSuite) TestJailSendQueuedTransaction() {
|
||||||
{
|
{
|
||||||
`["commands", "send"]`,
|
`["commands", "send"]`,
|
||||||
txParams,
|
txParams,
|
||||||
`{"result": {"context":{"` + jail.SendTransactionRequest + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
|
`{"result": {"context":{"` + params.SendTransactionMethodName + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`["commands", "getBalance"]`,
|
`["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"`
|
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)
|
// CompleteTransactionResult is a JSON returned from transaction complete function (used in exposed method)
|
||||||
type CompleteTransactionResult struct {
|
type CompleteTransactionResult struct {
|
||||||
ID string `json:"id"`
|
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
|
// makeJethIsConnectedHandler returns jeth.isConnected() handler
|
||||||
func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (response otto.Value) {
|
func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (response otto.Value) {
|
||||||
return func(call otto.FunctionCall) otto.Value {
|
return func(call otto.FunctionCall) otto.Value {
|
||||||
client, err := jail.requestManager.RPCClient()
|
client, err := jail.nodeManager.RPCClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
|
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package jail
|
package jail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"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/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/log"
|
"github.com/status-im/status-go/geth/log"
|
||||||
|
"github.com/status-im/status-go/geth/params"
|
||||||
"github.com/status-im/status-go/static"
|
"github.com/status-im/status-go/static"
|
||||||
|
|
||||||
"fknsrs.biz/p/ottoext/loop"
|
"fknsrs.biz/p/ottoext/loop"
|
||||||
|
@ -28,16 +32,21 @@ var (
|
||||||
type Jail struct {
|
type Jail struct {
|
||||||
// FIXME(tiabc): This mutex handles cells field access and must be renamed appropriately: cellsMutex
|
// FIXME(tiabc): This mutex handles cells field access and must be renamed appropriately: cellsMutex
|
||||||
sync.RWMutex
|
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
|
cells map[string]*JailCell // jail supports running many isolated instances of jailed runtime
|
||||||
baseJSCode string // JavaScript used to initialize all new cells with
|
baseJSCode string // JavaScript used to initialize all new cells with
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns new Jail environment.
|
// New returns new Jail environment with the associated NodeManager and
|
||||||
func New(nodeManager common.NodeManager) *Jail {
|
// AccountManager.
|
||||||
|
func New(nodeManager common.NodeManager, accountManager common.AccountManager) *Jail {
|
||||||
return &Jail{
|
return &Jail{
|
||||||
|
nodeManager: nodeManager,
|
||||||
|
accountManager: accountManager,
|
||||||
cells: make(map[string]*JailCell),
|
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.
|
// Send will serialize the first argument, send it to the node and returns the response.
|
||||||
// nolint: errcheck, unparam
|
// nolint: errcheck, unparam
|
||||||
func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
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.
|
// Remarshal the request into a Go value.
|
||||||
JSON, _ := call.Otto.Object("JSON")
|
JSON, _ := call.Otto.Object("JSON")
|
||||||
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwJSException(err.Error())
|
throwJSException(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rawReq = []byte(reqVal.String())
|
rawReq = []byte(reqVal.String())
|
||||||
reqs []RPCCall
|
reqs []common.RPCCall
|
||||||
batch bool
|
batch bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if rawReq[0] == '[' {
|
if rawReq[0] == '[' {
|
||||||
batch = true
|
batch = true
|
||||||
json.Unmarshal(rawReq, &reqs)
|
json.Unmarshal(rawReq, &reqs)
|
||||||
} else {
|
} else {
|
||||||
batch = false
|
batch = false
|
||||||
reqs = make([]RPCCall, 1)
|
reqs = make([]common.RPCCall, 1)
|
||||||
json.Unmarshal(rawReq, &reqs[0])
|
json.Unmarshal(rawReq, &reqs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the requests.
|
|
||||||
resps, _ := call.Otto.Object("new Array()")
|
resps, _ := call.Otto.Object("new Array()")
|
||||||
|
|
||||||
|
// Execute the requests.
|
||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
|
var resErr error
|
||||||
resp.Set("id", req.ID)
|
var res *otto.Object
|
||||||
var result json.RawMessage
|
|
||||||
|
|
||||||
// execute directly w/o RPC call to node
|
switch req.Method {
|
||||||
if req.Method == SendTransactionRequest {
|
case params.SendTransactionMethodName:
|
||||||
txHash, err := jail.requestManager.ProcessSendTransactionRequest(call.Otto, req)
|
res, resErr = jail.policy.ExecuteSendTransaction(req, call)
|
||||||
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(),
|
|
||||||
})
|
|
||||||
default:
|
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)
|
if resErr != nil {
|
||||||
jail.requestManager.PostProcessRequest(call.Otto, req, messageID)
|
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)
|
// Return the responses either to the callback (if supplied)
|
||||||
|
@ -261,18 +231,115 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
} else {
|
} else {
|
||||||
response, _ = resps.Get("0")
|
response, _ = resps.Get("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
if fn := call.Argument(1); fn.Class() == "Function" {
|
if fn := call.Argument(1); fn.Class() == "Function" {
|
||||||
fn.Call(otto.NullValue(), otto.NullValue(), response)
|
fn.Call(otto.NullValue(), otto.NullValue(), response)
|
||||||
return otto.UndefinedValue()
|
return otto.UndefinedValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
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
|
// 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}}
|
m := map[string]interface{}{"jsonrpc": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
||||||
res, _ := json.Marshal(m)
|
res, _ := json.Marshal(m)
|
||||||
val, _ := otto.Run("(" + string(res) + ")")
|
val, _ := vm.Run("(" + string(res) + ")")
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (s *JailTestSuite) TestJailLoopInCall() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.jail)
|
require.NotNil(s.jail)
|
||||||
|
|
||||||
s.StartTestNode(params.RopstenNetworkID)
|
s.StartTestNode(params.RopstenNetworkID, true)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
// 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() {
|
func (s *JailTestSuite) SetupTest() {
|
||||||
s.NodeManager = node.NewNodeManager()
|
require := s.Require()
|
||||||
s.Require().NotNil(s.NodeManager)
|
|
||||||
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
|
nodeManager := node.NewNodeManager()
|
||||||
s.jail = jail.New(s.NodeManager)
|
require.NotNil(nodeManager)
|
||||||
s.Require().NotNil(s.jail)
|
|
||||||
s.Require().IsType(&jail.Jail{}, s.jail)
|
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() {
|
func (s *JailTestSuite) TestInit() {
|
||||||
|
@ -119,7 +126,7 @@ func (s *JailTestSuite) TestJailRPCSend() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.jail)
|
require.NotNil(s.jail)
|
||||||
|
|
||||||
s.StartTestNode(params.RopstenNetworkID)
|
s.StartTestNode(params.RopstenNetworkID, false)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
// load Status JS and add test command to it
|
||||||
|
@ -154,7 +161,8 @@ func (s *JailTestSuite) TestIsConnected() {
|
||||||
require.NotNil(s.jail)
|
require.NotNil(s.jail)
|
||||||
|
|
||||||
// TODO(tiabc): Is this required?
|
// TODO(tiabc): Is this required?
|
||||||
s.StartTestNode(params.RopstenNetworkID)
|
s.StartTestNode(params.RopstenNetworkID, false)
|
||||||
|
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
s.jail.Parse(testChatID, "")
|
s.jail.Parse(testChatID, "")
|
||||||
|
|
|
@ -1,235 +1,234 @@
|
||||||
package jail
|
package jail
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
// gethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
// "github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
// "github.com/ethereum/go-ethereum/les/status"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
// "github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/robertkrimen/otto"
|
// "github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/common"
|
// "github.com/status-im/status-go/geth/common"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
// SendTransactionRequest is triggered on send transaction request
|
// // SendTransactionRequest is triggered on send transaction request
|
||||||
SendTransactionRequest = "eth_sendTransaction"
|
// SendTransactionRequest = "eth_sendTransaction"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// RequestManager represents interface to manage jailed requests.
|
// // RequestManager represents interface to manage jailed requests.
|
||||||
// Whenever some request passed to a Jail, needs to be pre/post processed,
|
// // Whenever some request passed to a Jail, needs to be pre/post processed,
|
||||||
// request manager is the right place for that.
|
// // request manager is the right place for that.
|
||||||
type RequestManager struct {
|
// type RequestManager struct {
|
||||||
nodeManager common.NodeManager
|
// nodeManager common.NodeManager
|
||||||
}
|
// }
|
||||||
|
|
||||||
// NewRequestManager returns a new instance of the RequestManager pointer.
|
// func NewRequestManager(nodeManager common.NodeManager) *RequestManager {
|
||||||
func NewRequestManager(nodeManager common.NodeManager) *RequestManager {
|
// return &RequestManager{
|
||||||
return &RequestManager{
|
// nodeManager: nodeManager,
|
||||||
nodeManager: nodeManager,
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// PreProcessRequest pre-processes a given RPC call to a given Otto VM
|
// // PreProcessRequest pre-processes a given RPC call to a given Otto VM
|
||||||
func (m *RequestManager) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
|
// func (m *RequestManager) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
|
||||||
messageID := currentMessageID(vm.Context())
|
// messageID := currentMessageID(vm.Context())
|
||||||
|
|
||||||
return messageID, nil
|
// return messageID, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// PostProcessRequest post-processes a given RPC call to a given Otto VM
|
// // PostProcessRequest post-processes a given RPC call to a given Otto VM
|
||||||
func (m *RequestManager) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
|
// 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 {
|
||||||
if len(messageID) > 0 {
|
// vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
|
||||||
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// set extra markers for queued transaction requests
|
// // set extra markers for queued transaction requests
|
||||||
if req.Method == SendTransactionRequest {
|
// if req.Method == SendTransactionRequest {
|
||||||
vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
|
// vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ProcessSendTransactionRequest processes send transaction request.
|
// // ProcessSendTransactionRequest processes send transaction request.
|
||||||
// Both pre and post processing happens within this function. Pre-processing
|
// // Both pre and post processing happens within this function. Pre-processing
|
||||||
// happens before transaction is send to backend, and post processing occurs
|
// // happens before transaction is send to backend, and post processing occurs
|
||||||
// when backend notifies that transaction sending is complete (either successfully
|
// // when backend notifies that transaction sending is complete (either successfully
|
||||||
// or with error)
|
// // or with error)
|
||||||
func (m *RequestManager) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (gethcommon.Hash, error) {
|
// func (m *RequestManager) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (gethcommon.Hash, error) {
|
||||||
lightEthereum, err := m.nodeManager.LightEthereumService()
|
// lightEthereum, err := m.nodeManager.LightEthereumService()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return gethcommon.Hash{}, err
|
// return gethcommon.Hash{}, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
backend := lightEthereum.StatusBackend
|
// backend := lightEthereum.StatusBackend
|
||||||
|
|
||||||
messageID, err := m.PreProcessRequest(vm, req)
|
// messageID, err := m.PreProcessRequest(vm, req)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return gethcommon.Hash{}, err
|
// 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
|
// // onSendTransactionRequest() will use context to obtain and release ticket
|
||||||
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
|
// ctx := context.Background()
|
||||||
if err != nil {
|
// ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
|
||||||
return gethcommon.Hash{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke post processing
|
// // this call blocks, up until Complete Transaction is called
|
||||||
m.PostProcessRequest(vm, req, messageID)
|
// 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.
|
// return txHash, nil
|
||||||
func (m *RequestManager) RPCClient() (*rpc.Client, error) {
|
// }
|
||||||
return m.nodeManager.RPCClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCCall represents RPC call parameters
|
// // RPCClient returns RPC client instance, creating it if necessary.
|
||||||
type RPCCall struct {
|
// func (m *RequestManager) RPCClient() (*rpc.Client, error) {
|
||||||
ID int64
|
// return m.nodeManager.RPCClient()
|
||||||
Method string
|
// }
|
||||||
Params []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
|
// // RPCCall represents RPC call parameters
|
||||||
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
|
// type RPCCall struct {
|
||||||
return status.SendTxArgs{}
|
// ID int64
|
||||||
}
|
// Method string
|
||||||
|
// Params []interface{}
|
||||||
|
// }
|
||||||
|
|
||||||
return status.SendTxArgs{
|
// func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
|
||||||
From: req.parseFromAddress(),
|
// if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
|
||||||
To: req.parseToAddress(),
|
// return status.SendTxArgs{}
|
||||||
Value: req.parseValue(),
|
// }
|
||||||
Data: req.parseData(),
|
|
||||||
Gas: req.parseGas(),
|
|
||||||
GasPrice: req.parseGasPrice(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r RPCCall) parseFromAddress() gethcommon.Address {
|
// return status.SendTxArgs{
|
||||||
params, ok := r.Params[0].(map[string]interface{})
|
// From: req.parseFromAddress(),
|
||||||
if !ok {
|
// To: req.parseToAddress(),
|
||||||
return gethcommon.HexToAddress("0x")
|
// Value: req.parseValue(),
|
||||||
}
|
// Data: req.parseData(),
|
||||||
|
// Gas: req.parseGas(),
|
||||||
|
// GasPrice: req.parseGasPrice(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
from, ok := params["from"].(string)
|
// func (r RPCCall) parseFromAddress() gethcommon.Address {
|
||||||
if !ok {
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
from = "0x"
|
// 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 {
|
// return gethcommon.HexToAddress(from)
|
||||||
params, ok := r.Params[0].(map[string]interface{})
|
// }
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
to, ok := params["to"].(string)
|
// func (r RPCCall) parseToAddress() *gethcommon.Address {
|
||||||
if !ok {
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
return nil
|
// if !ok {
|
||||||
}
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
address := gethcommon.HexToAddress(to)
|
// to, ok := params["to"].(string)
|
||||||
return &address
|
// if !ok {
|
||||||
}
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
func (r RPCCall) parseData() hexutil.Bytes {
|
// address := gethcommon.HexToAddress(to)
|
||||||
params, ok := r.Params[0].(map[string]interface{})
|
// return &address
|
||||||
if !ok {
|
// }
|
||||||
return hexutil.Bytes("0x")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, ok := params["data"].(string)
|
// func (r RPCCall) parseData() hexutil.Bytes {
|
||||||
if !ok {
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
data = "0x"
|
// if !ok {
|
||||||
}
|
// return hexutil.Bytes("0x")
|
||||||
|
// }
|
||||||
|
|
||||||
byteCode, err := hexutil.Decode(data)
|
// data, ok := params["data"].(string)
|
||||||
if err != nil {
|
// if !ok {
|
||||||
byteCode = hexutil.Bytes(data)
|
// data = "0x"
|
||||||
}
|
// }
|
||||||
|
|
||||||
return byteCode
|
// byteCode, err := hexutil.Decode(data)
|
||||||
}
|
// if err != nil {
|
||||||
|
// byteCode = hexutil.Bytes(data)
|
||||||
|
// }
|
||||||
|
|
||||||
// nolint: dupl
|
// return byteCode
|
||||||
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)
|
// // nolint: dupl
|
||||||
if !ok {
|
// func (r RPCCall) parseValue() *hexutil.Big {
|
||||||
return nil
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
}
|
// if !ok {
|
||||||
|
// return nil
|
||||||
|
// //return (*hexutil.Big)(big.NewInt("0x0"))
|
||||||
|
// }
|
||||||
|
|
||||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
// inputValue, ok := params["value"].(string)
|
||||||
if err != nil {
|
// if !ok {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (*hexutil.Big)(parsedValue)
|
// parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||||
}
|
// if err != nil {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
// nolint: dupl
|
// return (*hexutil.Big)(parsedValue)
|
||||||
func (r RPCCall) parseGas() *hexutil.Big {
|
// }
|
||||||
params, ok := r.Params[0].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inputValue, ok := params["gas"].(string)
|
// // nolint: dupl
|
||||||
if !ok {
|
// func (r RPCCall) parseGas() *hexutil.Big {
|
||||||
return nil
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
}
|
// if !ok {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
// inputValue, ok := params["gas"].(string)
|
||||||
if err != nil {
|
// if !ok {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (*hexutil.Big)(parsedValue)
|
// parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||||
}
|
// if err != nil {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
// nolint: dupl
|
// return (*hexutil.Big)(parsedValue)
|
||||||
func (r RPCCall) parseGasPrice() *hexutil.Big {
|
// }
|
||||||
params, ok := r.Params[0].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inputValue, ok := params["gasPrice"].(string)
|
// // nolint: dupl
|
||||||
if !ok {
|
// func (r RPCCall) parseGasPrice() *hexutil.Big {
|
||||||
return nil
|
// params, ok := r.Params[0].(map[string]interface{})
|
||||||
}
|
// if !ok {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
// inputValue, ok := params["gasPrice"].(string)
|
||||||
if err != nil {
|
// if !ok {
|
||||||
return nil
|
// 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
|
// return (*hexutil.Big)(parsedValue)
|
||||||
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 ""
|
// // 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
|
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) {
|
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return nil, ErrInvalidNodeManager
|
return nil, ErrInvalidNodeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config, err := m.NodeConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
m.RLock()
|
m.RLock()
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
|
||||||
|
@ -515,8 +520,20 @@ func (m *NodeManager) RPCClient() (*rpc.Client, error) {
|
||||||
if m.node == nil || m.nodeStarted == nil {
|
if m.node == nil || m.nodeStarted == nil {
|
||||||
return nil, ErrNoRunningNode
|
return nil, ErrNoRunningNode
|
||||||
}
|
}
|
||||||
|
|
||||||
<-m.nodeStarted
|
<-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 {
|
if m.rpcClient == nil {
|
||||||
var err error
|
var err error
|
||||||
m.rpcClient, err = m.node.Attach()
|
m.rpcClient, err = m.node.Attach()
|
||||||
|
|
|
@ -248,7 +248,7 @@ func (s *ManagerTestSuite) TestReferences() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// test with node fully started
|
// test with node fully started
|
||||||
s.StartTestNode(params.RinkebyNetworkID)
|
s.StartTestNode(params.RinkebyNetworkID, false)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
var nodeReadyTestCases = []struct {
|
var nodeReadyTestCases = []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -403,7 +403,7 @@ func (s *ManagerTestSuite) TestResetChainData() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.NodeManager)
|
require.NotNil(s.NodeManager)
|
||||||
|
|
||||||
s.StartTestNode(params.RinkebyNetworkID)
|
s.StartTestNode(params.RinkebyNetworkID, false)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // allow to sync for some time
|
time.Sleep(2 * time.Second) // allow to sync for some time
|
||||||
|
@ -422,7 +422,7 @@ func (s *ManagerTestSuite) TestRestartNode() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.NodeManager)
|
require.NotNil(s.NodeManager)
|
||||||
|
|
||||||
s.StartTestNode(params.RinkebyNetworkID)
|
s.StartTestNode(params.RinkebyNetworkID, false)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
s.True(s.NodeManager.IsNodeRunning())
|
s.True(s.NodeManager.IsNodeRunning())
|
||||||
|
|
|
@ -70,9 +70,15 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
|
||||||
return nil, ErrNodeMakeFailure
|
return nil, ErrNodeMakeFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// start Ethereum service
|
// start Ethereum service if we are not expected to use an upstream server.
|
||||||
if err := activateEthService(stack, config); err != nil {
|
if !config.UpstreamConfig.Enabled {
|
||||||
return nil, fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err)
|
|
||||||
|
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
|
// start Whisper service
|
||||||
|
@ -163,6 +169,7 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
|
||||||
ethConf.NetworkId = config.NetworkID
|
ethConf.NetworkId = config.NetworkID
|
||||||
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
|
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
|
||||||
ethConf.MaxPeers = config.MaxPeers
|
ethConf.MaxPeers = config.MaxPeers
|
||||||
|
|
||||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
lightEth, err := les.New(ctx, ðConf)
|
lightEth, err := les.New(ctx, ðConf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
"github.com/ethereum/go-ethereum/les/status"
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/log"
|
"github.com/status-im/status-go/geth/log"
|
||||||
|
"github.com/status-im/status-go/geth/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -60,27 +65,35 @@ func NewRPCManager(nodeManager common.NodeManager) *RPCManager {
|
||||||
|
|
||||||
// Call executes RPC request on node's in-proc RPC server
|
// Call executes RPC request on node's in-proc RPC server
|
||||||
func (c *RPCManager) Call(inputJSON string) string {
|
func (c *RPCManager) Call(inputJSON string) string {
|
||||||
server, err := c.nodeManager.RPCServer()
|
config, err := c.nodeManager.NodeConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.makeJSONErrorResponse(err)
|
return c.makeJSONErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow HTTP requests to block w/o
|
// allow HTTP requests to block w/o
|
||||||
outputJSON := make(chan string, 1)
|
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.
|
go func() {
|
||||||
if respStatus := rr.Code; respStatus != http.StatusOK {
|
body := bytes.NewBufferString(inputJSON)
|
||||||
log.Error("handler returned wrong status code", "got", respStatus, "want", http.StatusOK)
|
|
||||||
outputJSON <- c.makeJSONErrorResponse(ErrRPCServerCallFailed)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// everything is ok, return
|
outputJSON <- string(res)
|
||||||
outputJSON <- rr.Body.String()
|
return
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// wait till call is complete
|
// wait till call is complete
|
||||||
|
@ -94,6 +107,65 @@ func (c *RPCManager) Call(inputJSON string) string {
|
||||||
return c.makeJSONErrorResponse(ErrRPCServerTimeout)
|
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
|
// makeJSONErrorResponse returns error as JSON response
|
||||||
func (c *RPCManager) makeJSONErrorResponse(err error) string {
|
func (c *RPCManager) makeJSONErrorResponse(err error) string {
|
||||||
response := jsonErrResponse{
|
response := jsonErrResponse{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package node_test
|
package node_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/status-im/status-go/geth/log"
|
"github.com/status-im/status-go/geth/log"
|
||||||
|
@ -10,6 +11,14 @@ import (
|
||||||
"github.com/stretchr/testify/suite"
|
"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) {
|
func TestRPCTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(RPCTestSuite))
|
suite.Run(t, new(RPCTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -20,9 +29,11 @@ type RPCTestSuite struct {
|
||||||
|
|
||||||
func (s *RPCTestSuite) SetupTest() {
|
func (s *RPCTestSuite) SetupTest() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
s.NodeManager = node.NewNodeManager()
|
|
||||||
require.NotNil(s.NodeManager)
|
nodeManager := node.NewNodeManager()
|
||||||
require.IsType(&node.NodeManager{}, s.NodeManager)
|
require.NotNil(nodeManager)
|
||||||
|
|
||||||
|
s.NodeManager = nodeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RPCTestSuite) TestCallRPC() {
|
func (s *RPCTestSuite) TestCallRPC() {
|
||||||
|
@ -38,10 +49,13 @@ func (s *RPCTestSuite) TestCallRPC() {
|
||||||
nodeConfig.IPCEnabled = false
|
nodeConfig.IPCEnabled = false
|
||||||
nodeConfig.WSEnabled = false
|
nodeConfig.WSEnabled = false
|
||||||
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
|
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
|
||||||
|
|
||||||
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.NotNil(nodeConfig)
|
require.NotNil(nodeConfig)
|
||||||
|
|
||||||
defer s.NodeManager.StopNode()
|
defer s.NodeManager.StopNode()
|
||||||
|
|
||||||
<-nodeStarted
|
<-nodeStarted
|
||||||
|
|
||||||
progress := make(chan struct{}, 25)
|
progress := make(chan struct{}, 25)
|
||||||
|
|
|
@ -30,7 +30,7 @@ func (s *WhisperTestSuite) TestWhisperFilterRace() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.NodeManager)
|
require.NotNil(s.NodeManager)
|
||||||
|
|
||||||
s.StartTestNode(params.RinkebyNetworkID)
|
s.StartTestNode(params.RinkebyNetworkID, false)
|
||||||
defer s.StopTestNode()
|
defer s.StopTestNode()
|
||||||
|
|
||||||
whisperService, err := s.NodeManager.WhisperService()
|
whisperService, err := s.NodeManager.WhisperService()
|
||||||
|
|
|
@ -207,6 +207,20 @@ func (c *BootClusterConfig) String() string {
|
||||||
return string(data)
|
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
|
// NodeConfig stores configuration options for a node
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
// DevMode is true when given configuration is to be used during development.
|
// 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 defines whether logged info should also be output to os.Stderr
|
||||||
LogToStderr bool
|
LogToStderr bool
|
||||||
|
|
||||||
|
// UpstreamConfig extra config for providing upstream infura server.
|
||||||
|
UpstreamConfig UpstreamRPCConfig `json:"UpstreamConfig"`
|
||||||
|
|
||||||
// BootClusterConfig extra configuration for supporting cluster
|
// BootClusterConfig extra configuration for supporting cluster
|
||||||
BootClusterConfig *BootClusterConfig `json:"BootClusterConfig," validate:"structonly"`
|
BootClusterConfig *BootClusterConfig `json:"BootClusterConfig," validate:"structonly"`
|
||||||
|
|
||||||
|
@ -458,9 +475,15 @@ func (c *NodeConfig) updateConfig() error {
|
||||||
if err := c.updateGenesisConfig(); err != nil {
|
if err := c.updateGenesisConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.updateUpstreamConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.updateBootClusterConfig(); err != nil {
|
if err := c.updateBootClusterConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.updateRelativeDirsConfig(); err != nil {
|
if err := c.updateRelativeDirsConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -493,6 +516,27 @@ func (c *NodeConfig) updateGenesisConfig() error {
|
||||||
return nil
|
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.
|
// 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
|
// This is necessary until we have LES protocol support CHT sync, and better node
|
||||||
// discovery on mobile devices)
|
// discovery on mobile devices)
|
||||||
|
|
|
@ -72,6 +72,38 @@ var loadConfigTestCases = []struct {
|
||||||
require.Equal(t, "/foo/bar", nodeConfig.KeyStoreDir)
|
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`,
|
`test parameter overriding`,
|
||||||
`{
|
`{
|
||||||
|
|
|
@ -29,6 +29,9 @@ const (
|
||||||
// WSHost is a host interface for the websocket RPC server
|
// WSHost is a host interface for the websocket RPC server
|
||||||
WSHost = "localhost"
|
WSHost = "localhost"
|
||||||
|
|
||||||
|
// SendTransactionMethodName defines the name for a giving transaction.
|
||||||
|
SendTransactionMethodName = "eth_sendTransaction"
|
||||||
|
|
||||||
// WSPort is a WS-RPC port (replaced in unit tests)
|
// WSPort is a WS-RPC port (replaced in unit tests)
|
||||||
WSPort = 8546
|
WSPort = 8546
|
||||||
|
|
||||||
|
@ -84,6 +87,18 @@ const (
|
||||||
// FirebaseNotificationTriggerURL is URL where FCM notification requests are sent to
|
// FirebaseNotificationTriggerURL is URL where FCM notification requests are sent to
|
||||||
FirebaseNotificationTriggerURL = "https://fcm.googleapis.com/fcm/send"
|
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 is id of the main network
|
||||||
MainNetworkID = 1
|
MainNetworkID = 1
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
"LogFile": "geth.log",
|
"LogFile": "geth.log",
|
||||||
"LogLevel": "INFO",
|
"LogLevel": "INFO",
|
||||||
"LogToStderr": true,
|
"LogToStderr": true,
|
||||||
|
"UpstreamConfig": {
|
||||||
|
"Enabled": false,
|
||||||
|
"URL": "https://mainnet.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
|
||||||
|
},
|
||||||
"BootClusterConfig": {
|
"BootClusterConfig": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"RootNumber": 805,
|
"RootNumber": 805,
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
"LogFile": "geth.log",
|
"LogFile": "geth.log",
|
||||||
"LogLevel": "INFO",
|
"LogLevel": "INFO",
|
||||||
"LogToStderr": true,
|
"LogToStderr": true,
|
||||||
|
"UpstreamConfig": {
|
||||||
|
"Enabled": false,
|
||||||
|
"URL": "https://rinkeby.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
|
||||||
|
},
|
||||||
"BootClusterConfig": {
|
"BootClusterConfig": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"RootNumber": 66,
|
"RootNumber": 66,
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
"LogFile": "geth.log",
|
"LogFile": "geth.log",
|
||||||
"LogLevel": "INFO",
|
"LogLevel": "INFO",
|
||||||
"LogToStderr": true,
|
"LogToStderr": true,
|
||||||
|
"UpstreamConfig": {
|
||||||
|
"Enabled": false,
|
||||||
|
"URL": "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
|
||||||
|
},
|
||||||
"BootClusterConfig": {
|
"BootClusterConfig": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"RootNumber": 259,
|
"RootNumber": 259,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// TestConfig defines the default config usable at package-level.
|
||||||
TestConfig *common.TestConfig
|
TestConfig *common.TestConfig
|
||||||
|
|
||||||
// RootDir is the main application directory
|
// 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 {
|
type BaseTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
NodeManager common.NodeManager
|
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 := s.Require()
|
||||||
require.NotNil(s.NodeManager)
|
require.NotNil(s.NodeManager)
|
||||||
|
|
||||||
nodeConfig, err := MakeTestNodeConfig(networkID)
|
nodeConfig, err := MakeTestNodeConfig(networkID)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
|
nodeConfig.UpstreamConfig.Enabled = upstream
|
||||||
|
require.Equal(nodeConfig.UpstreamConfig.Enabled, upstream)
|
||||||
|
|
||||||
keyStoreDir := filepath.Join(TestDataDir, TestNetworkNames[networkID], "keystore")
|
keyStoreDir := filepath.Join(TestDataDir, TestNetworkNames[networkID], "keystore")
|
||||||
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
|
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
|
||||||
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
|
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
|
||||||
|
@ -78,6 +86,7 @@ func (s *BaseTestSuite) StartTestNode(networkID int) {
|
||||||
require.True(s.NodeManager.IsNodeRunning())
|
require.True(s.NodeManager.IsNodeRunning())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopTestNode attempts to stop initialized NodeManager.
|
||||||
func (s *BaseTestSuite) StopTestNode() {
|
func (s *BaseTestSuite) StopTestNode() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
require.NotNil(s.NodeManager)
|
require.NotNil(s.NodeManager)
|
||||||
|
@ -88,6 +97,7 @@ func (s *BaseTestSuite) StopTestNode() {
|
||||||
require.False(s.NodeManager.IsNodeRunning())
|
require.False(s.NodeManager.IsNodeRunning())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FirstBlockHash validates Attach operation for the NodeManager.
|
||||||
func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManager, expectedHash string) {
|
func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManager, expectedHash string) {
|
||||||
require.NotNil(nodeManager)
|
require.NotNil(nodeManager)
|
||||||
|
|
||||||
|
@ -110,6 +120,8 @@ func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManag
|
||||||
require.Equal(expectedHash, firstBlock.Hash.Hex())
|
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) {
|
func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
|
||||||
configJSON := `{
|
configJSON := `{
|
||||||
"NetworkId": ` + strconv.Itoa(networkID) + `,
|
"NetworkId": ` + strconv.Itoa(networkID) + `,
|
||||||
|
|
Loading…
Reference in New Issue