Refactor jail part 2 (#401)
Refactor jail so that it's more self-descriptive and easier to understand by newcomers. Also, the test coverage has been improved. Changes requiring status-react team actions: * Replace Parse calls with new CreateAndInitCell and ExecuteJS bindings, * Make sure web3.isConnected is ok as its response change to boolean value.
This commit is contained in:
parent
cb5ccb52c4
commit
086747a695
|
@ -145,7 +145,7 @@ func (s *APITestSuite) TestCellsRemovedAfterSwitchAccount() {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
for i := 0; i < itersCount; i++ {
|
for i := 0; i < itersCount; i++ {
|
||||||
_, e := s.api.JailManager().NewCell(getChatId(i))
|
_, e := s.api.JailManager().CreateCell(getChatId(i))
|
||||||
require.NoError(e)
|
require.NoError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ func (s *APITestSuite) TestLogoutRemovesCells() {
|
||||||
err = s.api.SelectAccount(address1, TestConfig.Account1.Password)
|
err = s.api.SelectAccount(address1, TestConfig.Account1.Password)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
s.api.JailManager().Parse(testChatID, ``)
|
s.api.JailManager().CreateAndInitCell(testChatID)
|
||||||
|
|
||||||
err = s.api.Logout()
|
err = s.api.Logout()
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
|
@ -42,8 +42,8 @@ func (s *JailRPCTestSuite) TestJailRPCSend() {
|
||||||
EnsureNodeSync(s.Backend.NodeManager())
|
EnsureNodeSync(s.Backend.NodeManager())
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
// load Status JS and add test command to it
|
||||||
s.jail.BaseJS(baseStatusJSCode)
|
s.jail.SetBaseJS(baseStatusJSCode)
|
||||||
s.jail.Parse(testChatID, ``)
|
s.jail.CreateAndInitCell(testChatID)
|
||||||
|
|
||||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||||
cell, err := s.jail.Cell(testChatID)
|
cell, err := s.jail.Cell(testChatID)
|
||||||
|
@ -72,7 +72,7 @@ func (s *JailRPCTestSuite) TestIsConnected() {
|
||||||
s.StartTestBackend()
|
s.StartTestBackend()
|
||||||
defer s.StopTestBackend()
|
defer s.StopTestBackend()
|
||||||
|
|
||||||
s.jail.Parse(testChatID, "")
|
s.jail.CreateAndInitCell(testChatID)
|
||||||
|
|
||||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||||
cell, err := s.jail.Cell(testChatID)
|
cell, err := s.jail.Cell(testChatID)
|
||||||
|
@ -87,11 +87,9 @@ func (s *JailRPCTestSuite) TestIsConnected() {
|
||||||
responseValue, err := cell.Get("responseValue")
|
responseValue, err := cell.Get("responseValue")
|
||||||
s.NoError(err, "cannot obtain result of isConnected()")
|
s.NoError(err, "cannot obtain result of isConnected()")
|
||||||
|
|
||||||
response, err := responseValue.ToString()
|
response, err := responseValue.ToBoolean()
|
||||||
s.NoError(err, "cannot parse result")
|
s.NoError(err, "cannot parse result")
|
||||||
|
s.True(response)
|
||||||
expectedResponse := `{"jsonrpc":"2.0","result":true}`
|
|
||||||
s.Equal(expectedResponse, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// regression test: eth_getTransactionReceipt with invalid transaction hash should return null
|
// regression test: eth_getTransactionReceipt with invalid transaction hash should return null
|
||||||
|
@ -115,7 +113,7 @@ func (s *JailRPCTestSuite) TestContractDeployment() {
|
||||||
EnsureNodeSync(s.Backend.NodeManager())
|
EnsureNodeSync(s.Backend.NodeManager())
|
||||||
|
|
||||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||||
s.jail.Parse(testChatID, "")
|
s.jail.CreateAndInitCell(testChatID)
|
||||||
|
|
||||||
cell, err := s.jail.Cell(testChatID)
|
cell, err := s.jail.Cell(testChatID)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
@ -251,9 +249,9 @@ func (s *JailRPCTestSuite) TestJailVMPersistence() {
|
||||||
}
|
}
|
||||||
|
|
||||||
jail := s.Backend.JailManager()
|
jail := s.Backend.JailManager()
|
||||||
jail.BaseJS(baseStatusJSCode)
|
jail.SetBaseJS(baseStatusJSCode)
|
||||||
|
|
||||||
parseResult := jail.Parse(testChatID, `
|
parseResult := jail.CreateAndInitCell(testChatID, `
|
||||||
var total = 0;
|
var total = 0;
|
||||||
_status_catalog['ping'] = function(params) {
|
_status_catalog['ping'] = function(params) {
|
||||||
total += Number(params.amount);
|
total += Number(params.amount);
|
||||||
|
|
|
@ -3,12 +3,17 @@ package jail
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/e2e"
|
||||||
"github.com/status-im/status-go/geth/common"
|
"github.com/status-im/status-go/geth/common"
|
||||||
"github.com/status-im/status-go/geth/jail"
|
"github.com/status-im/status-go/geth/jail"
|
||||||
|
"github.com/status-im/status-go/geth/node"
|
||||||
"github.com/status-im/status-go/geth/signal"
|
"github.com/status-im/status-go/geth/signal"
|
||||||
"github.com/status-im/status-go/static"
|
"github.com/status-im/status-go/static"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -27,60 +32,83 @@ func TestJailTestSuite(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type JailTestSuite struct {
|
type JailTestSuite struct {
|
||||||
suite.Suite
|
e2e.NodeManagerTestSuite
|
||||||
jail common.JailManager
|
Jail common.JailManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JailTestSuite) SetupTest() {
|
func (s *JailTestSuite) SetupTest() {
|
||||||
s.jail = jail.New(nil)
|
s.NodeManager = node.NewNodeManager()
|
||||||
s.NotNil(s.jail)
|
s.Jail = jail.New(s.NodeManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JailTestSuite) TestInit() {
|
func (s *JailTestSuite) TearDownTest() {
|
||||||
|
s.Jail.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestInitWithoutBaseJS() {
|
||||||
errorWrapper := func(err error) string {
|
errorWrapper := func(err error) string {
|
||||||
return `{"error":"` + err.Error() + `"}`
|
return `{"error":"` + err.Error() + `"}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// get cell VM w/o defining cell first
|
// get cell VM w/o defining cell first
|
||||||
cell, err := s.jail.Cell(testChatID)
|
cell, err := s.Jail.Cell(testChatID)
|
||||||
|
|
||||||
s.EqualError(err, "cell["+testChatID+"] doesn't exist")
|
s.EqualError(err, "cell '"+testChatID+"' not found")
|
||||||
s.Nil(cell)
|
s.Nil(cell)
|
||||||
|
|
||||||
// create VM (w/o properly initializing base JS script)
|
// create VM (w/o properly initializing base JS script)
|
||||||
err = errors.New("ReferenceError: '_status_catalog' is not defined")
|
err = errors.New("ReferenceError: '_status_catalog' is not defined")
|
||||||
s.Equal(errorWrapper(err), s.jail.Parse(testChatID, ``))
|
s.Equal(errorWrapper(err), s.Jail.CreateAndInitCell(testChatID, ``))
|
||||||
err = errors.New("ReferenceError: 'call' is not defined")
|
err = errors.New("ReferenceError: 'call' is not defined")
|
||||||
s.Equal(errorWrapper(err), s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`))
|
s.Equal(errorWrapper(err), s.Jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`))
|
||||||
|
|
||||||
// get existing cell (even though we got errors, cell was still created)
|
// get existing cell (even though we got errors, cell was still created)
|
||||||
cell, err = s.jail.Cell(testChatID)
|
cell, err = s.Jail.Cell(testChatID)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.NotNil(cell)
|
s.NotNil(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestInitWithBaseJS() {
|
||||||
statusJS := baseStatusJSCode + `;
|
statusJS := baseStatusJSCode + `;
|
||||||
_status_catalog.commands["testCommand"] = function (params) {
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
return params.val * params.val;
|
return params.val * params.val;
|
||||||
};`
|
};`
|
||||||
s.jail.BaseJS(statusJS)
|
s.Jail.SetBaseJS(statusJS)
|
||||||
|
|
||||||
// now no error should occur
|
// now no error should occur
|
||||||
response := s.jail.Parse(testChatID, ``)
|
response := s.Jail.CreateAndInitCell(testChatID)
|
||||||
expectedResponse := `{"result": {"commands":{},"responses":{}}}`
|
expectedResponse := `{"result": {"commands":{},"responses":{}}}`
|
||||||
s.Equal(expectedResponse, response)
|
s.Equal(expectedResponse, response)
|
||||||
|
|
||||||
// make sure that Call succeeds even w/o running node
|
// make sure that Call succeeds even w/o running node
|
||||||
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
|
response = s.Jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
|
||||||
expectedResponse = `{"result": 144}`
|
expectedResponse = `{"result": 144}`
|
||||||
s.Equal(expectedResponse, response)
|
s.Equal(expectedResponse, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JailTestSuite) TestParse() {
|
// @TODO(adam): finally, this test should pass as checking existence of `_status_catalog`
|
||||||
|
// should be done in status-react.
|
||||||
|
func (s *JailTestSuite) TestCreateAndInitCellWithoutStatusCatalog() {
|
||||||
|
response := s.Jail.CreateAndInitCell(testChatID)
|
||||||
|
s.Equal(`{"error":"ReferenceError: '_status_catalog' is not defined"}`, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO(adam): remove extra JS when checking `_status_catalog` is move to status-react.
|
||||||
|
func (s *JailTestSuite) TestMultipleInitError() {
|
||||||
|
response := s.Jail.CreateAndInitCell(testChatID, `var _status_catalog = {}`)
|
||||||
|
s.Equal(`{"result": {}}`, response)
|
||||||
|
|
||||||
|
response = s.Jail.CreateAndInitCell(testChatID)
|
||||||
|
s.Equal(`{"error":"cell with id 'testChat' already exists"}`, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO(adam): remove extra JS when checking `_status_catalog` is moved to status-react.
|
||||||
|
func (s *JailTestSuite) TestCreateAndInitCellResponse() {
|
||||||
extraCode := `
|
extraCode := `
|
||||||
var _status_catalog = {
|
var _status_catalog = {
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
};`
|
};`
|
||||||
response := s.jail.Parse("newChat", extraCode)
|
response := s.Jail.CreateAndInitCell("newChat", extraCode)
|
||||||
expectedResponse := `{"result": {"foo":"bar"}}`
|
expectedResponse := `{"result": {"foo":"bar"}}`
|
||||||
s.Equal(expectedResponse, response)
|
s.Equal(expectedResponse, response)
|
||||||
}
|
}
|
||||||
|
@ -91,24 +119,27 @@ func (s *JailTestSuite) TestFunctionCall() {
|
||||||
_status_catalog.commands["testCommand"] = function (params) {
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
return params.val * params.val;
|
return params.val * params.val;
|
||||||
};`
|
};`
|
||||||
s.jail.Parse(testChatID, statusJS)
|
s.Jail.CreateAndInitCell(testChatID, statusJS)
|
||||||
|
|
||||||
// call with wrong chat id
|
// call with wrong chat id
|
||||||
response := s.jail.Call("chatIDNonExistent", "", "")
|
response := s.Jail.Call("chatIDNonExistent", "", "")
|
||||||
expectedError := `{"error":"cell[chatIDNonExistent] doesn't exist"}`
|
expectedError := `{"error":"cell 'chatIDNonExistent' not found"}`
|
||||||
s.Equal(expectedError, response)
|
s.Equal(expectedError, response)
|
||||||
|
|
||||||
// call extraFunc()
|
// call extraFunc()
|
||||||
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
|
response = s.Jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
|
||||||
expectedResponse := `{"result": 144}`
|
expectedResponse := `{"result": 144}`
|
||||||
s.Equal(expectedResponse, response)
|
s.Equal(expectedResponse, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *JailTestSuite) TestEventSignal() {
|
func (s *JailTestSuite) TestEventSignal() {
|
||||||
s.jail.Parse(testChatID, "")
|
s.StartTestNode()
|
||||||
|
defer s.StopTestNode()
|
||||||
|
|
||||||
|
s.Jail.CreateAndInitCell(testChatID)
|
||||||
|
|
||||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||||
cell, err := s.jail.Cell(testChatID)
|
cell, err := s.Jail.Cell(testChatID)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
testData := "foobar"
|
testData := "foobar"
|
||||||
|
@ -154,10 +185,69 @@ func (s *JailTestSuite) TestEventSignal() {
|
||||||
response, err := responseValue.ToString()
|
response, err := responseValue.ToString()
|
||||||
s.NoError(err, "cannot parse result")
|
s.NoError(err, "cannot parse result")
|
||||||
|
|
||||||
expectedResponse := `{"jsonrpc":"2.0","result":true}`
|
expectedResponse := `{"result":true}`
|
||||||
s.Equal(expectedResponse, response)
|
s.Equal(expectedResponse, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCallResponseOrder tests exactly the problem from
|
||||||
|
// https://github.com/status-im/status-go/issues/372
|
||||||
|
func (s *JailTestSuite) TestSendSyncResponseOrder() {
|
||||||
|
s.StartTestNode()
|
||||||
|
defer s.StopTestNode()
|
||||||
|
|
||||||
|
// `testCommand` is a simple JS function. `calculateGasPrice` makes
|
||||||
|
// an implicit JSON-RPC call via `send` handler (it's a sync call).
|
||||||
|
// `web3.eth.gasPrice` is chosen to call `send` handler under the hood
|
||||||
|
// because it's a simple RPC method and does not require any params.
|
||||||
|
statusJS := baseStatusJSCode + `;
|
||||||
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
|
return params.val * params.val;
|
||||||
|
};
|
||||||
|
_status_catalog.commands["calculateGasPrice"] = function (n) {
|
||||||
|
var gasMultiplicator = Math.pow(1.4, n).toFixed(3);
|
||||||
|
var price = 211000000000;
|
||||||
|
try {
|
||||||
|
price = web3.eth.gasPrice;
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
return price * gasMultiplicator;
|
||||||
|
};
|
||||||
|
`
|
||||||
|
s.Jail.CreateAndInitCell(testChatID, statusJS)
|
||||||
|
|
||||||
|
// Concurrently call `testCommand` and `calculateGasPrice` and do some assertions.
|
||||||
|
// If the code executed in cell's VM is not thread-safe, this test will likely panic.
|
||||||
|
N := 10
|
||||||
|
errCh := make(chan error, N)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < N; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
res := s.Jail.Call(testChatID, `["commands", "testCommand"]`, fmt.Sprintf(`{"val": %d}`, i))
|
||||||
|
if !strings.Contains(string(res), fmt.Sprintf("result\": %d", i*i)) {
|
||||||
|
errCh <- fmt.Errorf("result should be '%d', got %s", i*i, res)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
res := s.Jail.Call(testChatID, `["commands", "calculateGasPrice"]`, fmt.Sprintf(`%d`, i))
|
||||||
|
if strings.Contains(string(res), "error") {
|
||||||
|
errCh <- fmt.Errorf("result should not contain 'error', got %s", res)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
close(errCh)
|
||||||
|
for e := range errCh {
|
||||||
|
s.NoError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *JailTestSuite) TestJailCellsRemovedAfterStop() {
|
func (s *JailTestSuite) TestJailCellsRemovedAfterStop() {
|
||||||
const loopLen = 5
|
const loopLen = 5
|
||||||
|
|
||||||
|
@ -167,8 +257,8 @@ func (s *JailTestSuite) TestJailCellsRemovedAfterStop() {
|
||||||
require := s.Require()
|
require := s.Require()
|
||||||
|
|
||||||
for i := 0; i < loopLen; i++ {
|
for i := 0; i < loopLen; i++ {
|
||||||
s.jail.Parse(getTestCellID(i), "")
|
s.Jail.CreateAndInitCell(getTestCellID(i))
|
||||||
cell, err := s.jail.Cell(getTestCellID(i))
|
cell, err := s.Jail.Cell(getTestCellID(i))
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
_, err = cell.Run(`
|
_, err = cell.Run(`
|
||||||
var counter = 1;
|
var counter = 1;
|
||||||
|
@ -179,10 +269,10 @@ func (s *JailTestSuite) TestJailCellsRemovedAfterStop() {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.jail.Stop()
|
s.Jail.Stop()
|
||||||
|
|
||||||
for i := 0; i < loopLen; i++ {
|
for i := 0; i < loopLen; i++ {
|
||||||
_, err := s.jail.Cell(getTestCellID(i))
|
_, err := s.Jail.Cell(getTestCellID(i))
|
||||||
require.Error(err, "Expected cells removing (from Jail) after stop")
|
require.Error(err, "Expected cells removing (from Jail) after stop")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,37 +27,22 @@ func (s *RPCClientTestSuite) TestNewClient() {
|
||||||
config, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
config, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
nodeStarted, err := s.NodeManager.StartNode(config)
|
// upstream disabled
|
||||||
s.NoError(err)
|
|
||||||
<-nodeStarted
|
|
||||||
|
|
||||||
node, err := s.NodeManager.Node()
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
// upstream disabled, local node ok
|
|
||||||
s.False(config.UpstreamConfig.Enabled)
|
s.False(config.UpstreamConfig.Enabled)
|
||||||
_, err = rpc.NewClient(node, config.UpstreamConfig)
|
_, err = rpc.NewClient(nil, config.UpstreamConfig)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// upstream enabled with incorrect URL, local node ok
|
// upstream enabled with correct URL
|
||||||
upstreamBad := config.UpstreamConfig
|
|
||||||
upstreamBad.Enabled = true
|
|
||||||
upstreamBad.URL = "///__httphh://///incorrect_urlxxx"
|
|
||||||
_, err = rpc.NewClient(node, upstreamBad)
|
|
||||||
s.Error(err)
|
|
||||||
|
|
||||||
// upstream enabled with correct URL, local node ok
|
|
||||||
upstreamGood := config.UpstreamConfig
|
upstreamGood := config.UpstreamConfig
|
||||||
upstreamGood.Enabled = true
|
upstreamGood.Enabled = true
|
||||||
upstreamGood.URL = "http://example.com/rpc"
|
upstreamGood.URL = "http://example.com/rpc"
|
||||||
_, err = rpc.NewClient(node, upstreamGood)
|
_, err = rpc.NewClient(nil, upstreamGood)
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// upstream disabled, local node failed (stopped)
|
// upstream enabled with incorrect URL
|
||||||
nodeStopped, err := s.NodeManager.StopNode()
|
upstreamBad := config.UpstreamConfig
|
||||||
s.NoError(err)
|
upstreamBad.Enabled = true
|
||||||
<-nodeStopped
|
upstreamBad.URL = "///__httphh://///incorrect_urlxxx"
|
||||||
|
_, err = rpc.NewClient(nil, upstreamBad)
|
||||||
_, err = rpc.NewClient(node, config.UpstreamConfig)
|
|
||||||
s.Error(err)
|
s.Error(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,7 @@ func (s *WhisperJailTestSuite) StartTestBackend(opts ...e2e.TestNodeOption) {
|
||||||
s.WhisperAPI = whisper.NewPublicWhisperAPI(s.WhisperService())
|
s.WhisperAPI = whisper.NewPublicWhisperAPI(s.WhisperService())
|
||||||
s.Jail = s.Backend.JailManager()
|
s.Jail = s.Backend.JailManager()
|
||||||
s.NotNil(s.Jail)
|
s.NotNil(s.Jail)
|
||||||
|
s.Jail.SetBaseJS(baseStatusJSCode)
|
||||||
s.Jail.BaseJS(baseStatusJSCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WhisperJailTestSuite) AddKeyPair(address, password string) (string, error) {
|
func (s *WhisperJailTestSuite) AddKeyPair(address, password string) (string, error) {
|
||||||
|
@ -291,7 +290,8 @@ func (s *WhisperJailTestSuite) TestJailWhisper() {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
chatID := crypto.Keccak256Hash([]byte(tc.name)).Hex()
|
chatID := crypto.Keccak256Hash([]byte(tc.name)).Hex()
|
||||||
s.Jail.Parse(chatID, makeTopicCode)
|
|
||||||
|
s.Jail.CreateAndInitCell(chatID, makeTopicCode)
|
||||||
|
|
||||||
cell, err := s.Jail.Cell(chatID)
|
cell, err := s.Jail.Cell(chatID)
|
||||||
s.NoError(err, "cannot get VM")
|
s.NoError(err, "cannot get VM")
|
||||||
|
|
|
@ -179,10 +179,10 @@ func (api *StatusAPI) DiscardTransactions(ids []common.QueuedTxID) map[common.Qu
|
||||||
return api.b.txQueueManager.DiscardTransactions(ids)
|
return api.b.txQueueManager.DiscardTransactions(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JailParse creates a new jail cell context, with the given chatID as identifier.
|
// CreateAndInitCell creates a new jail cell context, with the given chatID as identifier.
|
||||||
// New context executes provided JavaScript code, right after the initialization.
|
// New context executes provided JavaScript code, right after the initialization.
|
||||||
func (api *StatusAPI) JailParse(chatID string, js string) string {
|
func (api *StatusAPI) CreateAndInitCell(chatID, js string) string {
|
||||||
return api.b.jailManager.Parse(chatID, js)
|
return api.b.jailManager.CreateAndInitCell(chatID, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JailCall executes given JavaScript function w/i a jail cell context identified by the chatID.
|
// JailCall executes given JavaScript function w/i a jail cell context identified by the chatID.
|
||||||
|
@ -190,9 +190,14 @@ func (api *StatusAPI) JailCall(chatID, this, args string) string {
|
||||||
return api.b.jailManager.Call(chatID, this, args)
|
return api.b.jailManager.Call(chatID, this, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JailBaseJS allows to setup initial JavaScript to be loaded on each jail.Parse()
|
// JailExecute allows to run arbitrary JS code within a jail cell.
|
||||||
func (api *StatusAPI) JailBaseJS(js string) {
|
func (api *StatusAPI) JailExecute(chatID, code string) string {
|
||||||
api.b.jailManager.BaseJS(js)
|
return api.b.jailManager.Execute(chatID, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJailBaseJS allows to setup initial JavaScript to be loaded on each jail.CreateAndInitCell().
|
||||||
|
func (api *StatusAPI) SetJailBaseJS(js string) {
|
||||||
|
api.b.jailManager.SetBaseJS(js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify sends a push notification to the device with the given token.
|
// Notify sends a push notification to the device with the given token.
|
||||||
|
|
|
@ -285,26 +285,29 @@ type JailCell interface {
|
||||||
// Call an arbitrary JS function by name and args.
|
// Call an arbitrary JS function by name and args.
|
||||||
Call(item string, this interface{}, args ...interface{}) (otto.Value, error)
|
Call(item string, this interface{}, args ...interface{}) (otto.Value, error)
|
||||||
// Stop stops background execution of cell.
|
// Stop stops background execution of cell.
|
||||||
Stop()
|
Stop() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// JailManager defines methods for managing jailed environments
|
// JailManager defines methods for managing jailed environments
|
||||||
type JailManager interface {
|
type JailManager interface {
|
||||||
// Parse creates a new jail cell context, with the given chatID as identifier.
|
|
||||||
// New context executes provided JavaScript code, right after the initialization.
|
|
||||||
Parse(chatID, js string) string
|
|
||||||
|
|
||||||
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
||||||
Call(chatID, this, args string) string
|
Call(chatID, this, args string) string
|
||||||
|
|
||||||
// NewCell initializes and returns a new jail cell.
|
// CreateCell creates a new jail cell.
|
||||||
NewCell(chatID string) (JailCell, error)
|
CreateCell(chatID string) (JailCell, error)
|
||||||
|
|
||||||
|
// CreateAndInitCell creates a new jail cell and initialize it
|
||||||
|
// with web3 and other handlers.
|
||||||
|
CreateAndInitCell(chatID string, code ...string) string
|
||||||
|
|
||||||
// Cell returns an existing instance of JailCell.
|
// Cell returns an existing instance of JailCell.
|
||||||
Cell(chatID string) (JailCell, error)
|
Cell(chatID string) (JailCell, error)
|
||||||
|
|
||||||
// BaseJS allows to setup initial JavaScript to be loaded on each jail.Parse()
|
// Execute allows to run arbitrary JS code within a cell.
|
||||||
BaseJS(js string)
|
Execute(chatID, code string) string
|
||||||
|
|
||||||
|
// SetBaseJS allows to setup initial JavaScript to be loaded on each jail.CreateAndInitCell().
|
||||||
|
SetBaseJS(js string)
|
||||||
|
|
||||||
// Stop stops all background activity of jail
|
// Stop stops all background activity of jail
|
||||||
Stop()
|
Stop()
|
||||||
|
|
|
@ -2,6 +2,8 @@ package jail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/jail/internal/fetch"
|
"github.com/status-im/status-go/geth/jail/internal/fetch"
|
||||||
|
@ -16,49 +18,68 @@ type Cell struct {
|
||||||
*vm.VM
|
*vm.VM
|
||||||
id string
|
id string
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
lo *loop.Loop
|
|
||||||
|
loop *loop.Loop
|
||||||
|
loopStopped chan struct{}
|
||||||
|
loopErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCell encapsulates what we need to create a new jailCell from the
|
// NewCell encapsulates what we need to create a new jailCell from the
|
||||||
// provided vm and eventloop instance.
|
// provided vm and eventloop instance.
|
||||||
func newCell(id string, ottoVM *otto.Otto) (*Cell, error) {
|
func NewCell(id string) (*Cell, error) {
|
||||||
cellVM := vm.New(ottoVM)
|
vm := vm.New()
|
||||||
|
lo := loop.New(vm)
|
||||||
|
|
||||||
lo := loop.New(cellVM)
|
err := registerVMHandlers(vm, lo)
|
||||||
|
|
||||||
err := registerVMHandlers(cellVM, lo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
loopStopped := make(chan struct{})
|
||||||
|
cell := Cell{
|
||||||
|
VM: vm,
|
||||||
|
id: id,
|
||||||
|
cancel: cancel,
|
||||||
|
loop: lo,
|
||||||
|
loopStopped: loopStopped,
|
||||||
|
}
|
||||||
|
|
||||||
// start event loop in background
|
// Start event loop in the background.
|
||||||
go lo.Run(ctx) //nolint: errcheck
|
go func() {
|
||||||
|
err := lo.Run(ctx)
|
||||||
|
if err != context.Canceled {
|
||||||
|
cell.loopErr = err
|
||||||
|
}
|
||||||
|
|
||||||
return &Cell{
|
close(loopStopped)
|
||||||
VM: cellVM,
|
}()
|
||||||
id: id,
|
|
||||||
cancel: cancel,
|
return &cell, nil
|
||||||
lo: lo,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerHandlers register variuous functions and handlers
|
// registerHandlers register variuous functions and handlers
|
||||||
// to the Otto VM, such as Fetch API callbacks or promises.
|
// to the Otto VM, such as Fetch API callbacks or promises.
|
||||||
func registerVMHandlers(v *vm.VM, lo *loop.Loop) error {
|
func registerVMHandlers(vm *vm.VM, lo *loop.Loop) error {
|
||||||
// setTimeout/setInterval functions
|
// setTimeout/setInterval functions
|
||||||
if err := timers.Define(v, lo); err != nil {
|
if err := timers.Define(vm, lo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAPI functions
|
// FetchAPI functions
|
||||||
return fetch.Define(v, lo)
|
return fetch.Define(vm, lo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop halts event loop associated with cell.
|
// Stop halts event loop associated with cell.
|
||||||
func (c *Cell) Stop() {
|
func (c *Cell) Stop() error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-c.loopStopped:
|
||||||
|
return c.loopErr
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
return errors.New("stopping the cell timed out")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallAsync puts otto's function with given args into
|
// CallAsync puts otto's function with given args into
|
||||||
|
@ -67,7 +88,9 @@ func (c *Cell) Stop() {
|
||||||
// async call, like callback.
|
// async call, like callback.
|
||||||
func (c *Cell) CallAsync(fn otto.Value, args ...interface{}) {
|
func (c *Cell) CallAsync(fn otto.Value, args ...interface{}) {
|
||||||
task := looptask.NewCallTask(fn, args...)
|
task := looptask.NewCallTask(fn, args...)
|
||||||
c.lo.Add(task)
|
// Add a task to the queue.
|
||||||
// TODO(divan): review API of `loop` package, it's contrintuitive
|
c.loop.Add(task)
|
||||||
go c.lo.Ready(task)
|
// And run the task immediately.
|
||||||
|
// It's a blocking operation.
|
||||||
|
c.loop.Ready(task)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package jail_test
|
package jail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -7,254 +7,92 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/jail"
|
|
||||||
"github.com/status-im/status-go/static"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
testChatID = "testChat"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCellTestSuite(t *testing.T) {
|
func TestCellTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(CellTestSuite))
|
suite.Run(t, new(CellTestSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
type CellTestSuite struct {
|
type CellTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
jail *jail.Jail
|
cell *Cell
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CellTestSuite) SetupTest() {
|
func (s *CellTestSuite) SetupTest() {
|
||||||
s.jail = jail.New(nil)
|
cell, err := NewCell("testCell1")
|
||||||
s.NotNil(s.jail)
|
s.NoError(err)
|
||||||
|
s.NotNil(cell)
|
||||||
|
|
||||||
|
s.cell = cell
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CellTestSuite) TestJailTimeout() {
|
func (s *CellTestSuite) TearDownTest() {
|
||||||
require := s.Require()
|
err := s.cell.Stop()
|
||||||
|
s.NoError(err)
|
||||||
cell, err := s.jail.NewCell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
// Attempt to run a timeout string against a Cell.
|
|
||||||
_, err = cell.Run(`
|
|
||||||
var timerCounts = 0;
|
|
||||||
setTimeout(function(n){
|
|
||||||
if (Date.now() - n < 50) {
|
|
||||||
throw new Error("Timed out");
|
|
||||||
}
|
|
||||||
|
|
||||||
timerCounts++;
|
|
||||||
}, 50, Date.now());
|
|
||||||
`)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
// wait at least 10x longer to decrease probability
|
|
||||||
// of false negatives as we using real clock here
|
|
||||||
time.Sleep(300 * time.Millisecond)
|
|
||||||
|
|
||||||
value, err := cell.Get("timerCounts")
|
|
||||||
require.NoError(err)
|
|
||||||
require.True(value.IsNumber())
|
|
||||||
require.Equal("1", value.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CellTestSuite) TestJailLoopInCall() {
|
func (s *CellTestSuite) TestCellRegisteredHandlers() {
|
||||||
require := s.Require()
|
_, err := s.cell.Run(`setTimeout(function(){}, 100)`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
_, err = s.cell.Run(`fetch`)
|
||||||
s.jail.BaseJS(baseStatusJSCode)
|
s.NoError(err)
|
||||||
s.jail.Parse(testChatID, ``)
|
|
||||||
|
|
||||||
cell, err := s.jail.Cell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
items := make(chan string)
|
|
||||||
|
|
||||||
err = cell.Set("__captureResponse", func(val string) otto.Value {
|
|
||||||
go func() { items <- val }()
|
|
||||||
return otto.UndefinedValue()
|
|
||||||
})
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
_, err = cell.Run(`
|
|
||||||
function callRunner(namespace){
|
|
||||||
console.log("Initiating callRunner for: ", namespace)
|
|
||||||
return setTimeout(function(){
|
|
||||||
__captureResponse(namespace);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
_, err = cell.Call("callRunner", nil, "softball")
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case received := <-items:
|
|
||||||
require.Equal(received, "softball")
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
require.Fail("Failed to received event response")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJailLoopRace tests multiple setTimeout callbacks,
|
// TestJailLoopRace tests multiple setTimeout callbacks,
|
||||||
// supposed to be run with '-race' flag.
|
// supposed to be run with '-race' flag.
|
||||||
func (s *CellTestSuite) TestJailLoopRace() {
|
func (s *CellTestSuite) TestCellLoopRace() {
|
||||||
require := s.Require()
|
cell := s.cell
|
||||||
|
|
||||||
cell, err := s.jail.NewCell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
items := make(chan struct{})
|
items := make(chan struct{})
|
||||||
|
|
||||||
err = cell.Set("__captureResponse", func() otto.Value {
|
err := cell.Set("__captureResponse", func() otto.Value {
|
||||||
go func() { items <- struct{}{} }()
|
items <- struct{}{}
|
||||||
return otto.UndefinedValue()
|
return otto.UndefinedValue()
|
||||||
})
|
})
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
_, err = cell.Run(`
|
_, err = cell.Run(`
|
||||||
function callRunner(){
|
function callRunner(){
|
||||||
return setTimeout(function(){
|
return setTimeout(function(){
|
||||||
__captureResponse();
|
__captureResponse();
|
||||||
}, 1000);
|
}, 200);
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
_, err = cell.Call("callRunner", nil)
|
_, err = cell.Call("callRunner", nil)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
select {
|
select {
|
||||||
case <-items:
|
case <-items:
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(400 * time.Millisecond):
|
||||||
require.Fail("test timed out")
|
s.Fail("test timed out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CellTestSuite) TestJailFetchPromise() {
|
|
||||||
body := `{"key": "value"}`
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(body)) //nolint: errcheck
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
require := s.Require()
|
|
||||||
|
|
||||||
cell, err := s.jail.NewCell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
dataCh := make(chan otto.Value, 1)
|
|
||||||
errCh := make(chan otto.Value, 1)
|
|
||||||
|
|
||||||
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
|
||||||
require.NoError(err)
|
|
||||||
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
// run JS code for fetching valid URL
|
|
||||||
_, err = cell.Run(`fetch('` + server.URL + `').then(function(r) {
|
|
||||||
return r.text()
|
|
||||||
}).then(function(data) {
|
|
||||||
__captureSuccess(data)
|
|
||||||
}).catch(function (e) {
|
|
||||||
__captureError(e)
|
|
||||||
})`)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case data := <-dataCh:
|
|
||||||
require.True(data.IsString())
|
|
||||||
require.Equal(body, data.String())
|
|
||||||
case err := <-errCh:
|
|
||||||
require.Fail("request failed", err)
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
require.Fail("test timed out")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CellTestSuite) TestJailFetchCatch() {
|
|
||||||
require := s.Require()
|
|
||||||
|
|
||||||
cell, err := s.jail.NewCell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
dataCh := make(chan otto.Value, 1)
|
|
||||||
errCh := make(chan otto.Value, 1)
|
|
||||||
|
|
||||||
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
|
||||||
require.NoError(err)
|
|
||||||
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
// run JS code for fetching invalid URL
|
|
||||||
_, err = cell.Run(`fetch('http://👽/nonexistent').then(function(r) {
|
|
||||||
return r.text()
|
|
||||||
}).then(function(data) {
|
|
||||||
__captureSuccess(data)
|
|
||||||
}).catch(function (e) {
|
|
||||||
__captureError(e)
|
|
||||||
})`)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case data := <-dataCh:
|
|
||||||
require.Fail("request should have failed, but returned", data)
|
|
||||||
case e := <-errCh:
|
|
||||||
require.True(e.IsObject())
|
|
||||||
name, err := e.Object().Get("name")
|
|
||||||
require.NoError(err)
|
|
||||||
require.Equal("Error", name.String())
|
|
||||||
_, err = e.Object().Get("message")
|
|
||||||
require.NoError(err)
|
|
||||||
case <-time.After(3 * time.Second):
|
|
||||||
require.Fail("test timed out")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestJailFetchRace tests multiple fetch callbacks,
|
// TestJailFetchRace tests multiple fetch callbacks,
|
||||||
// supposed to be run with '-race' flag.
|
// supposed to be run with '-race' flag.
|
||||||
func (s *CellTestSuite) TestJailFetchRace() {
|
func (s *CellTestSuite) TestCellFetchRace() {
|
||||||
body := `{"key": "value"}`
|
body := `{"key": "value"}`
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.Write([]byte(body)) //nolint: errcheck
|
w.Write([]byte(body)) //nolint: errcheck
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
require := s.Require()
|
|
||||||
|
|
||||||
cell, err := s.jail.NewCell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
defer cell.Stop()
|
|
||||||
|
|
||||||
|
cell := s.cell
|
||||||
dataCh := make(chan otto.Value, 1)
|
dataCh := make(chan otto.Value, 1)
|
||||||
errCh := make(chan otto.Value, 1)
|
errCh := make(chan otto.Value, 1)
|
||||||
|
|
||||||
err = cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
err := cell.Set("__captureSuccess", func(res otto.Value) { dataCh <- res })
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
err = cell.Set("__captureError", func(res otto.Value) { errCh <- res })
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// run JS code for fetching valid URL
|
// run JS code for fetching valid URL
|
||||||
_, err = cell.Run(`fetch('` + server.URL + `').then(function(r) {
|
_, err = cell.Run(`fetch('` + server.URL + `').then(function(r) {
|
||||||
|
@ -264,7 +102,7 @@ func (s *CellTestSuite) TestJailFetchRace() {
|
||||||
}).catch(function (e) {
|
}).catch(function (e) {
|
||||||
__captureError(e)
|
__captureError(e)
|
||||||
})`)
|
})`)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// run JS code for fetching invalid URL
|
// run JS code for fetching invalid URL
|
||||||
_, err = cell.Run(`fetch('http://👽/nonexistent').then(function(r) {
|
_, err = cell.Run(`fetch('http://👽/nonexistent').then(function(r) {
|
||||||
|
@ -274,74 +112,93 @@ func (s *CellTestSuite) TestJailFetchRace() {
|
||||||
}).catch(function (e) {
|
}).catch(function (e) {
|
||||||
__captureError(e)
|
__captureError(e)
|
||||||
})`)
|
})`)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
select {
|
select {
|
||||||
case data := <-dataCh:
|
case data := <-dataCh:
|
||||||
require.True(data.IsString())
|
s.Equal(body, data.String())
|
||||||
require.Equal(body, data.String())
|
|
||||||
case e := <-errCh:
|
case e := <-errCh:
|
||||||
require.True(e.IsObject())
|
|
||||||
name, err := e.Object().Get("name")
|
name, err := e.Object().Get("name")
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
require.Equal("Error", name.String())
|
s.Equal("Error", name.String())
|
||||||
_, err = e.Object().Get("message")
|
_, err = e.Object().Get("message")
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
case <-time.After(3 * time.Second):
|
case <-time.After(5 * time.Second):
|
||||||
require.Fail("test timed out")
|
s.Fail("test timed out")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJailLoopCancel tests that cell.Stop() really cancels event
|
// TestCellLoopCancel tests that cell.Stop() really cancels event
|
||||||
// loop and pending tasks.
|
// loop and pending tasks.
|
||||||
func (s *CellTestSuite) TestJailLoopCancel() {
|
func (s *CellTestSuite) TestCellLoopCancel() {
|
||||||
require := s.Require()
|
cell := s.cell
|
||||||
|
|
||||||
// load Status JS and add test command to it
|
|
||||||
s.jail.BaseJS(baseStatusJSCode)
|
|
||||||
s.jail.Parse(testChatID, ``)
|
|
||||||
|
|
||||||
cell, err := s.jail.Cell(testChatID)
|
|
||||||
require.NoError(err)
|
|
||||||
require.NotNil(cell)
|
|
||||||
|
|
||||||
|
var err error
|
||||||
var count int
|
var count int
|
||||||
err = cell.Set("__captureResponse", func(val string) otto.Value { //nolint: unparam
|
|
||||||
|
err = cell.Set("__captureResponse", func(call otto.FunctionCall) otto.Value {
|
||||||
count++
|
count++
|
||||||
return otto.UndefinedValue()
|
return otto.UndefinedValue()
|
||||||
})
|
})
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
_, err = cell.Run(`
|
_, err = cell.Run(`
|
||||||
function callRunner(val, delay){
|
function callRunner(delay){
|
||||||
return setTimeout(function(){
|
return setTimeout(function(){
|
||||||
__captureResponse(val);
|
__captureResponse();
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
// Run 5 timeout tasks to be executed in: 1, 2, 3, 4 and 5 secs
|
// Run 5 timeout tasks to be executed in: 1, 2, 3, 4 and 5 secs
|
||||||
for i := 1; i <= 5; i++ {
|
for i := 1; i <= 5; i++ {
|
||||||
_, err = cell.Call("callRunner", nil, "value", i*1000)
|
_, err = cell.Call("callRunner", nil, i*1000)
|
||||||
require.NoError(err)
|
s.NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait 1.5 second (so only one task executed) so far
|
// Wait 1.5 second (so only one task executed) so far
|
||||||
// and stop the cell (event loop should die)
|
// and stop the cell (event loop should die)
|
||||||
time.Sleep(1500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
cell.Stop()
|
err = cell.Stop()
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
// check that only 1 task has increased counter
|
// check that only 1 task has increased counter
|
||||||
require.Equal(1, count)
|
s.Equal(1, count)
|
||||||
|
|
||||||
// wait 2 seconds more (so at least two more tasks would
|
// wait 2 seconds more (so at least two more tasks would
|
||||||
// have been executed if event loop is still running)
|
// have been executed if event loop is still running)
|
||||||
<-time.After(2 * time.Second)
|
<-time.After(2 * time.Second)
|
||||||
|
|
||||||
// check that counter hasn't increased
|
// check that counter hasn't increased
|
||||||
require.Equal(1, count)
|
s.Equal(1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CellTestSuite) TestCellCallAsync() {
|
||||||
|
// Don't use buffered channel as it's supposed to be an async call.
|
||||||
|
datac := make(chan string)
|
||||||
|
|
||||||
|
err := s.cell.Set("testCallAsync", func(call otto.FunctionCall) otto.Value {
|
||||||
|
datac <- call.Argument(0).String()
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
fn, err := s.cell.Get("testCallAsync")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.cell.CallAsync(fn, "success")
|
||||||
|
s.Equal("success", <-datac)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CellTestSuite) TestCellCallStopMultipleTimes() {
|
||||||
|
s.NotPanics(func() {
|
||||||
|
err := s.cell.Stop()
|
||||||
|
s.NoError(err)
|
||||||
|
err = s.cell.Stop()
|
||||||
|
s.NoError(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,130 +4,179 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
"github.com/status-im/status-go/geth/common"
|
|
||||||
"github.com/status-im/status-go/geth/jail/console"
|
"github.com/status-im/status-go/geth/jail/console"
|
||||||
"github.com/status-im/status-go/geth/node"
|
|
||||||
"github.com/status-im/status-go/geth/signal"
|
"github.com/status-im/status-go/geth/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// signals
|
|
||||||
const (
|
const (
|
||||||
|
// EventSignal is a signal from jail.
|
||||||
EventSignal = "jail.signal"
|
EventSignal = "jail.signal"
|
||||||
|
// eventConsoleLog defines the event type for the console.log call.
|
||||||
// EventConsoleLog defines the event type for the console.log call.
|
|
||||||
eventConsoleLog = "vm.console.log"
|
eventConsoleLog = "vm.console.log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerHandlers augments and transforms a given jail cell's underlying VM,
|
// registerWeb3Provider creates an object called "jeth",
|
||||||
// by adding and replacing method handlers.
|
// which is a web3.js provider.
|
||||||
func registerHandlers(jail *Jail, cell common.JailCell, chatID string) error {
|
func registerWeb3Provider(jail *Jail, cell *Cell) error {
|
||||||
jeth, err := cell.Get("jeth")
|
jeth := map[string]interface{}{
|
||||||
if err != nil {
|
"console": map[string]interface{}{
|
||||||
return err
|
"log": func(fn otto.FunctionCall) otto.Value {
|
||||||
}
|
return console.Write(fn, os.Stdout, eventConsoleLog)
|
||||||
|
},
|
||||||
registerHandler := jeth.Object().Set
|
|
||||||
|
|
||||||
if err = registerHandler("console", map[string]interface{}{
|
|
||||||
"log": func(fn otto.FunctionCall) otto.Value {
|
|
||||||
return console.Write(fn, os.Stdout, eventConsoleLog)
|
|
||||||
},
|
},
|
||||||
}); err != nil {
|
"send": createSendHandler(jail, cell),
|
||||||
return err
|
"sendAsync": createSendAsyncHandler(jail, cell),
|
||||||
|
"isConnected": createIsConnectedHandler(jail),
|
||||||
}
|
}
|
||||||
|
|
||||||
// register send handler
|
return cell.Set("jeth", jeth)
|
||||||
if err = registerHandler("send", makeSendHandler(jail)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// register sendAsync handler
|
|
||||||
if err = registerHandler("sendAsync", makeAsyncSendHandler(jail, cell)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// register isConnected handler
|
|
||||||
if err = registerHandler("isConnected", makeJethIsConnectedHandler(jail, cell)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// register sendMessage/showSuggestions handlers
|
|
||||||
if err = cell.Set("statusSignals", struct{}{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
statusSignals, err := cell.Get("statusSignals")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
registerHandler = statusSignals.Object().Set
|
|
||||||
|
|
||||||
return registerHandler("sendSignal", makeSignalHandler(chatID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeAsyncSendHandler returns jeth.sendAsync() handler.
|
// registerStatusSignals creates an object called "statusSignals".
|
||||||
func makeAsyncSendHandler(jail *Jail, cellInt common.JailCell) func(call otto.FunctionCall) otto.Value {
|
// TODO(adam): describe what it is and when it's used.
|
||||||
// FIXME(tiabc): Get rid of this.
|
func registerStatusSignals(cell *Cell) error {
|
||||||
cell := cellInt.(*Cell)
|
statusSignals := map[string]interface{}{
|
||||||
return func(call otto.FunctionCall) otto.Value {
|
"sendSignal": createSendSignalHandler(cell),
|
||||||
go func() {
|
}
|
||||||
response := jail.Send(call)
|
|
||||||
|
|
||||||
|
return cell.Set("statusSignals", statusSignals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSendHandler returns jeth.send().
|
||||||
|
func createSendHandler(jail *Jail, cell *Cell) func(call otto.FunctionCall) otto.Value {
|
||||||
|
return func(call otto.FunctionCall) otto.Value {
|
||||||
|
// As it's a sync call, it's called already from a thread-safe context,
|
||||||
|
// thus using otto.Otto directly. Otherwise, it would try to acquire a lock again
|
||||||
|
// and result in a deadlock.
|
||||||
|
vm := cell.VM.UnsafeVM()
|
||||||
|
|
||||||
|
request, err := vm.Call("JSON.stringify", nil, call.Argument(0))
|
||||||
|
if err != nil {
|
||||||
|
throwJSError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := jail.sendRPCCall(request.String())
|
||||||
|
if err != nil {
|
||||||
|
throwJSError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := vm.ToValue(response)
|
||||||
|
if err != nil {
|
||||||
|
throwJSError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSendAsyncHandler returns jeth.sendAsync() handler.
|
||||||
|
func createSendAsyncHandler(jail *Jail, cell *Cell) func(call otto.FunctionCall) otto.Value {
|
||||||
|
return func(call otto.FunctionCall) otto.Value {
|
||||||
|
// As it's a sync call, it's called already from a thread-safe context,
|
||||||
|
// thus using otto.Otto directly. Otherwise, it would try to acquire a lock again
|
||||||
|
// and result in a deadlock.
|
||||||
|
unsafeVM := cell.VM.UnsafeVM()
|
||||||
|
|
||||||
|
request, err := unsafeVM.Call("JSON.stringify", nil, call.Argument(0))
|
||||||
|
if err != nil {
|
||||||
|
throwJSError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// As it's an async call, it's not called from a thread-safe context,
|
||||||
|
// thus using a thread-safe vm.VM.
|
||||||
|
vm := cell.VM
|
||||||
callback := call.Argument(1)
|
callback := call.Argument(1)
|
||||||
if callback.Class() == "Function" {
|
response, err := jail.sendRPCCall(request.String())
|
||||||
// run callback asyncronously with args (error, response)
|
|
||||||
err := otto.NullValue()
|
// If provided callback argument is not a function, don't call it.
|
||||||
cell.CallAsync(callback, err, response)
|
if callback.Class() != "Function" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
cell.CallAsync(callback, vm.MakeCustomError("Error", err.Error()))
|
||||||
|
} else {
|
||||||
|
cell.CallAsync(callback, nil, response)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return otto.UndefinedValue()
|
return otto.UndefinedValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeSendHandler returns jeth.send() and jeth.sendAsync() handler
|
// createIsConnectedHandler returns jeth.isConnected() handler.
|
||||||
func makeSendHandler(jail *Jail) func(call otto.FunctionCall) otto.Value {
|
// This handler returns `true` if client is actively listening for network connections.
|
||||||
|
func createIsConnectedHandler(jail RPCClientProvider) func(call otto.FunctionCall) otto.Value {
|
||||||
return func(call otto.FunctionCall) otto.Value {
|
return func(call otto.FunctionCall) otto.Value {
|
||||||
return jail.Send(call)
|
client := jail.RPCClient()
|
||||||
}
|
if client == nil {
|
||||||
}
|
throwJSError(ErrNoRPCClient)
|
||||||
|
}
|
||||||
// makeJethIsConnectedHandler returns jeth.isConnected() handler
|
|
||||||
func makeJethIsConnectedHandler(jail *Jail, cellInt common.JailCell) func(call otto.FunctionCall) (response otto.Value) {
|
|
||||||
// FIXME(tiabc): Get rid of this.
|
|
||||||
cell := cellInt.(*Cell)
|
|
||||||
return func(call otto.FunctionCall) otto.Value {
|
|
||||||
client := jail.nodeManager.RPCClient()
|
|
||||||
|
|
||||||
var netListeningResult bool
|
var netListeningResult bool
|
||||||
if err := client.Call(&netListeningResult, "net_listening"); err != nil {
|
if err := client.Call(&netListeningResult, "net_listening"); err != nil {
|
||||||
return newErrorResponseOtto(cell.VM, err.Error(), nil)
|
throwJSError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !netListeningResult {
|
if netListeningResult {
|
||||||
return newErrorResponseOtto(cell.VM, node.ErrNoRunningNode.Error(), nil)
|
return otto.TrueValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return newResultResponse(call.Otto, true)
|
return otto.FalseValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignalEvent wraps Jail send signals
|
func createSendSignalHandler(cell *Cell) func(otto.FunctionCall) otto.Value {
|
||||||
type SignalEvent struct {
|
|
||||||
ChatID string `json:"chat_id"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSignalHandler(chatID string) func(call otto.FunctionCall) otto.Value {
|
|
||||||
return func(call otto.FunctionCall) otto.Value {
|
return func(call otto.FunctionCall) otto.Value {
|
||||||
message := call.Argument(0).String()
|
message := call.Argument(0).String()
|
||||||
|
|
||||||
signal.Send(signal.Envelope{
|
signal.Send(signal.Envelope{
|
||||||
Type: EventSignal,
|
Type: EventSignal,
|
||||||
Event: SignalEvent{
|
Event: struct {
|
||||||
ChatID: chatID,
|
ChatID string `json:"chat_id"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}{
|
||||||
|
ChatID: cell.id,
|
||||||
Data: message,
|
Data: message,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return newResultResponse(call.Otto, true)
|
// As it's a sync call, it's called already from a thread-safe context,
|
||||||
|
// thus using otto.Otto directly. Otherwise, it would try to acquire a lock again
|
||||||
|
// and result in a deadlock.
|
||||||
|
vm := cell.VM.UnsafeVM()
|
||||||
|
|
||||||
|
value, err := wrapResultInValue(vm, otto.TrueValue())
|
||||||
|
if err != nil {
|
||||||
|
throwJSError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// throwJSError calls panic with an error string. It should be called
|
||||||
|
// only in a context that handles panics like otto.Otto.
|
||||||
|
func throwJSError(err error) {
|
||||||
|
value, err := otto.ToValue(err.Error())
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapResultInValue(vm *otto.Otto, result interface{}) (value otto.Value, err error) {
|
||||||
|
value, err = vm.Run(`({})`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = value.Object().Set("result", result)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package jail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/status-im/status-go/geth/params"
|
||||||
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
|
"github.com/status-im/status-go/geth/signal"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandlersTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(HandlersTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlersTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
responseFixture string
|
||||||
|
ts *httptest.Server
|
||||||
|
tsCalls int
|
||||||
|
client *gethrpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) SetupTest() {
|
||||||
|
s.responseFixture = `{"json-rpc":"2.0","id":10,"result":true}`
|
||||||
|
s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.tsCalls++
|
||||||
|
fmt.Fprintln(w, s.responseFixture)
|
||||||
|
}))
|
||||||
|
|
||||||
|
client, err := gethrpc.Dial(s.ts.URL)
|
||||||
|
s.NoError(err)
|
||||||
|
s.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TearDownTest() {
|
||||||
|
s.ts.Close()
|
||||||
|
s.tsCalls = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3SendHandlerSuccess() {
|
||||||
|
client, err := rpc.NewClient(s.client, params.UpstreamRPCConfig{})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
jail := New(&testRPCClientProvider{client})
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// web3.eth.syncing is an arbitrary web3 sync RPC call.
|
||||||
|
value, err := cell.Run("web3.eth.syncing")
|
||||||
|
s.NoError(err)
|
||||||
|
result, err := value.ToBoolean()
|
||||||
|
s.NoError(err)
|
||||||
|
s.True(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3SendHandlerFailure() {
|
||||||
|
jail := New(nil)
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
_, err = cell.Run("web3.eth.syncing")
|
||||||
|
s.Error(err, ErrNoRPCClient.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3SendAsyncHandlerSuccess() {
|
||||||
|
client, err := rpc.NewClient(s.client, params.UpstreamRPCConfig{})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
jail := New(&testRPCClientProvider{client})
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
errc := make(chan string)
|
||||||
|
resultc := make(chan string)
|
||||||
|
err = cell.Set("__getSyncingCallback", func(call otto.FunctionCall) otto.Value {
|
||||||
|
errc <- call.Argument(0).String()
|
||||||
|
resultc <- call.Argument(1).String()
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
_, err = cell.Run(`web3.eth.getSyncing(__getSyncingCallback)`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.Equal(`null`, <-errc)
|
||||||
|
s.Equal(`true`, <-resultc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3SendAsyncHandlerWithoutCallbackSuccess() {
|
||||||
|
client, err := rpc.NewClient(s.client, params.UpstreamRPCConfig{})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
jail := New(&testRPCClientProvider{client})
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
_, err = cell.Run(`web3.eth.getSyncing()`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// As there is no callback, it's not possible to detect when
|
||||||
|
// the request hit the server.
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
s.Equal(1, s.tsCalls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3SendAsyncHandlerFailure() {
|
||||||
|
jail := New(nil)
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
errc := make(chan otto.Value)
|
||||||
|
resultc := make(chan string)
|
||||||
|
err = cell.Set("__getSyncingCallback", func(call otto.FunctionCall) otto.Value {
|
||||||
|
errc <- call.Argument(0)
|
||||||
|
resultc <- call.Argument(1).String()
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
_, err = cell.Run(`web3.eth.getSyncing(__getSyncingCallback)`)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
errValue := <-errc
|
||||||
|
message, err := errValue.Object().Get("message")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
s.Equal(ErrNoRPCClient.Error(), message.String())
|
||||||
|
s.Equal(`undefined`, <-resultc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestWeb3IsConnectedHandler() {
|
||||||
|
client, err := rpc.NewClient(s.client, params.UpstreamRPCConfig{})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
jail := New(&testRPCClientProvider{client})
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// When result is true.
|
||||||
|
value, err := cell.Run("web3.isConnected()")
|
||||||
|
s.NoError(err)
|
||||||
|
valueBoolean, err := value.ToBoolean()
|
||||||
|
s.NoError(err)
|
||||||
|
s.True(valueBoolean)
|
||||||
|
|
||||||
|
// When result is false.
|
||||||
|
s.responseFixture = `{"json-rpc":"2.0","id":10,"result":false}`
|
||||||
|
value, err = cell.Run("web3.isConnected()")
|
||||||
|
s.NoError(err)
|
||||||
|
valueBoolean, err = value.ToBoolean()
|
||||||
|
s.NoError(err)
|
||||||
|
s.False(valueBoolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlersTestSuite) TestSendSignalHandler() {
|
||||||
|
jail := New(nil)
|
||||||
|
|
||||||
|
cell, err := jail.createAndInitCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||||
|
s.Contains(jsonEvent, "test signal message")
|
||||||
|
})
|
||||||
|
|
||||||
|
value, err := cell.Run(`statusSignals.sendSignal("test signal message")`)
|
||||||
|
s.NoError(err)
|
||||||
|
result, err := value.Object().Get("result")
|
||||||
|
s.NoError(err)
|
||||||
|
resultBool, err := result.ToBoolean()
|
||||||
|
s.NoError(err)
|
||||||
|
s.True(resultBool)
|
||||||
|
}
|
|
@ -207,8 +207,7 @@ func (s *FetchSuite) SetupTest() {
|
||||||
s.mux = http.NewServeMux()
|
s.mux = http.NewServeMux()
|
||||||
s.srv = httptest.NewServer(s.mux)
|
s.srv = httptest.NewServer(s.mux)
|
||||||
|
|
||||||
o := otto.New()
|
s.vm = vm.New()
|
||||||
s.vm = vm.New(o)
|
|
||||||
s.loop = loop.New(s.vm)
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
go s.loop.Run(context.Background()) //nolint: errcheck
|
go s.loop.Run(context.Background()) //nolint: errcheck
|
||||||
|
|
|
@ -90,6 +90,15 @@ func (l *Loop) removeByID(id int64) {
|
||||||
l.lock.Unlock()
|
l.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Loop) removeAll() {
|
||||||
|
l.lock.Lock()
|
||||||
|
for _, t := range l.tasks {
|
||||||
|
t.Cancel()
|
||||||
|
}
|
||||||
|
l.tasks = make(map[int64]Task)
|
||||||
|
l.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Ready signals to the loop that a task is ready to be finalised. This might
|
// Ready signals to the loop that a task is ready to be finalised. This might
|
||||||
// block if the "ready channel" in the loop is at capacity.
|
// block if the "ready channel" in the loop is at capacity.
|
||||||
func (l *Loop) Ready(t Task) {
|
func (l *Loop) Ready(t Task) {
|
||||||
|
@ -108,9 +117,7 @@ func (l *Loop) processTask(t Task) error {
|
||||||
|
|
||||||
if err := t.Execute(l.vm, l); err != nil {
|
if err := t.Execute(l.vm, l); err != nil {
|
||||||
l.lock.RLock()
|
l.lock.RLock()
|
||||||
for _, t := range l.tasks {
|
t.Cancel()
|
||||||
t.Cancel()
|
|
||||||
}
|
|
||||||
l.lock.RUnlock()
|
l.lock.RUnlock()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -127,6 +134,11 @@ func (l *Loop) Run(ctx context.Context) error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case t := <-l.ready:
|
case t := <-l.ready:
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
l.removeAll()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
if t == nil {
|
if t == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -140,7 +152,8 @@ func (l *Loop) Run(ctx context.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return context.Canceled
|
l.removeAll()
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,9 +121,9 @@ func (c CallTask) Execute(vm *vm.VM, l *loop.Loop) error {
|
||||||
// FunctionCall in CallTask likely does use it,
|
// FunctionCall in CallTask likely does use it,
|
||||||
// so we must to guard it here
|
// so we must to guard it here
|
||||||
vm.Lock()
|
vm.Lock()
|
||||||
defer vm.Unlock()
|
|
||||||
|
|
||||||
v, err := c.Function.Call(otto.NullValue(), c.Args...)
|
v, err := c.Function.Call(otto.NullValue(), c.Args...)
|
||||||
|
vm.Unlock()
|
||||||
|
|
||||||
c.Value <- v
|
c.Value <- v
|
||||||
c.Error <- err
|
c.Error <- err
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/status-im/status-go/geth/jail/internal/loop"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
|
@ -85,8 +84,7 @@ type PromiseSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PromiseSuite) SetupTest() {
|
func (s *PromiseSuite) SetupTest() {
|
||||||
o := otto.New()
|
s.vm = vm.New()
|
||||||
s.vm = vm.New(o)
|
|
||||||
s.loop = loop.New(s.vm)
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
go s.loop.Run(context.Background()) //nolint: errcheck
|
go s.loop.Run(context.Background()) //nolint: errcheck
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/geth/jail/internal/loop"
|
"github.com/status-im/status-go/geth/jail/internal/loop"
|
||||||
"github.com/status-im/status-go/geth/jail/internal/timers"
|
"github.com/status-im/status-go/geth/jail/internal/timers"
|
||||||
"github.com/status-im/status-go/geth/jail/internal/vm"
|
"github.com/status-im/status-go/geth/jail/internal/vm"
|
||||||
|
@ -103,8 +101,7 @@ type TimersSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimersSuite) SetupTest() {
|
func (s *TimersSuite) SetupTest() {
|
||||||
o := otto.New()
|
s.vm = vm.New()
|
||||||
s.vm = vm.New(o)
|
|
||||||
s.loop = loop.New(s.vm)
|
s.loop = loop.New(s.vm)
|
||||||
|
|
||||||
go s.loop.Run(context.Background()) //nolint: errcheck
|
go s.loop.Run(context.Background()) //nolint: errcheck
|
||||||
|
|
|
@ -15,12 +15,17 @@ type VM struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates new instance of VM.
|
// New creates new instance of VM.
|
||||||
func New(vm *otto.Otto) *VM {
|
func New() *VM {
|
||||||
return &VM{
|
return &VM{
|
||||||
vm: vm,
|
vm: otto.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsafeVM returns a thread-unsafe JavaScript VM.
|
||||||
|
func (vm *VM) UnsafeVM() *otto.Otto {
|
||||||
|
return vm.vm
|
||||||
|
}
|
||||||
|
|
||||||
// Set sets the value to be keyed by the provided keyname.
|
// Set sets the value to be keyed by the provided keyname.
|
||||||
func (vm *VM) Set(key string, val interface{}) error {
|
func (vm *VM) Set(key string, val interface{}) error {
|
||||||
vm.Lock()
|
vm.Lock()
|
||||||
|
@ -77,3 +82,11 @@ func (vm *VM) ToValue(value interface{}) (otto.Value, error) {
|
||||||
|
|
||||||
return vm.vm.ToValue(value)
|
return vm.vm.ToValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeCustomError allows to create a new Error object.
|
||||||
|
func (vm *VM) MakeCustomError(name, message string) otto.Value {
|
||||||
|
vm.Lock()
|
||||||
|
defer vm.Unlock()
|
||||||
|
|
||||||
|
return vm.vm.MakeCustomError(name, message)
|
||||||
|
}
|
||||||
|
|
|
@ -4,251 +4,281 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"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/jail/internal/vm"
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
"github.com/status-im/status-go/geth/log"
|
|
||||||
"github.com/status-im/status-go/static"
|
"github.com/status-im/status-go/static"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
// FIXME(tiabc): Get rid of this global variable. Move it to a constructor or initialization.
|
web3InstanceCode = `
|
||||||
web3JSCode = static.MustAsset("scripts/web3.js")
|
var Web3 = require('web3');
|
||||||
|
var web3 = new Web3(jeth);
|
||||||
//ErrInvalidJail - error jail init env
|
var Bignumber = require("bignumber.js");
|
||||||
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
function bn(val) {
|
||||||
|
return new Bignumber(val);
|
||||||
|
}
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Jail represents jailed environment inside of which we hold multiple cells.
|
var (
|
||||||
// Each cell is a separate JavaScript VM.
|
web3Code = string(static.MustAsset("scripts/web3.js"))
|
||||||
|
// ErrNoRPCClient is returned when an RPC client is required but it's nil.
|
||||||
|
ErrNoRPCClient = errors.New("RPC client is not available")
|
||||||
|
)
|
||||||
|
|
||||||
|
// RPCClientProvider is an interface that provides a way
|
||||||
|
// to obtain an rpc.Client.
|
||||||
|
type RPCClientProvider interface {
|
||||||
|
RPCClient() *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jail manages multiple JavaScript execution contexts (JavaScript VMs) called cells.
|
||||||
|
// Each cell is a separate VM with web3.js set up.
|
||||||
|
//
|
||||||
|
// As rpc.Client might not be available during Jail initialization,
|
||||||
|
// a provider function is used.
|
||||||
type Jail struct {
|
type Jail struct {
|
||||||
nodeManager common.NodeManager
|
rpcClientProvider RPCClientProvider
|
||||||
baseJSCode string // JavaScript used to initialize all new cells with
|
baseJS string
|
||||||
|
cellsMx sync.RWMutex
|
||||||
cellsMx sync.RWMutex
|
cells map[string]*Cell
|
||||||
cells map[string]*Cell // jail supports running many isolated instances of jailed runtime
|
|
||||||
|
|
||||||
vm *vm.VM // vm for internal otto related tasks (see Send method)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns new Jail environment with the associated NodeManager.
|
// New returns a new Jail.
|
||||||
// It's caller responsibility to call jail.Stop() when jail is not needed.
|
func New(provider RPCClientProvider) *Jail {
|
||||||
func New(nodeManager common.NodeManager) *Jail {
|
return NewWithBaseJS(provider, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithBaseJS returns a new Jail with base JS configured.
|
||||||
|
func NewWithBaseJS(provider RPCClientProvider, code string) *Jail {
|
||||||
return &Jail{
|
return &Jail{
|
||||||
nodeManager: nodeManager,
|
rpcClientProvider: provider,
|
||||||
cells: make(map[string]*Cell),
|
baseJS: code,
|
||||||
vm: vm.New(otto.New()),
|
cells: make(map[string]*Cell),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseJS allows to setup initial JavaScript to be loaded on each jail.Parse().
|
// SetBaseJS sets initial JavaScript code loaded to each new cell.
|
||||||
func (jail *Jail) BaseJS(js string) {
|
func (j *Jail) SetBaseJS(js string) {
|
||||||
jail.baseJSCode = js
|
j.baseJS = js
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCell initializes and returns a new jail cell.
|
// Stop stops jail and all assosiacted cells.
|
||||||
func (jail *Jail) NewCell(chatID string) (common.JailCell, error) {
|
func (j *Jail) Stop() {
|
||||||
if jail == nil {
|
j.cellsMx.Lock()
|
||||||
return nil, ErrInvalidJail
|
defer j.cellsMx.Unlock()
|
||||||
|
|
||||||
|
for _, cell := range j.cells {
|
||||||
|
cell.Stop() //nolint: errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
cellVM := otto.New()
|
// TODO(tiabc): Move this initialisation to a proper place.
|
||||||
|
j.cells = make(map[string]*Cell)
|
||||||
|
}
|
||||||
|
|
||||||
cell, err := newCell(chatID, cellVM)
|
// createCell creates a new cell if it does not exists.
|
||||||
|
func (j *Jail) createCell(chatID string) (*Cell, error) {
|
||||||
|
j.cellsMx.Lock()
|
||||||
|
defer j.cellsMx.Unlock()
|
||||||
|
|
||||||
|
if cell, ok := j.cells[chatID]; ok {
|
||||||
|
return cell, fmt.Errorf("cell with id '%s' already exists", chatID)
|
||||||
|
}
|
||||||
|
|
||||||
|
cell, err := NewCell(chatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jail.cellsMx.Lock()
|
j.cells[chatID] = cell
|
||||||
jail.cells[chatID] = cell
|
|
||||||
jail.cellsMx.Unlock()
|
|
||||||
|
|
||||||
return cell, nil
|
return cell, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops jail and all assosiacted cells.
|
// CreateCell creates a new cell. It returns an error
|
||||||
func (jail *Jail) Stop() {
|
// if a cell with a given ID already exists.
|
||||||
jail.cellsMx.Lock()
|
func (j *Jail) CreateCell(chatID string) (common.JailCell, error) {
|
||||||
defer jail.cellsMx.Unlock()
|
return j.createCell(chatID)
|
||||||
|
|
||||||
for _, cell := range jail.cells {
|
|
||||||
cell.Stop()
|
|
||||||
}
|
|
||||||
// TODO(tiabc): Move this initialisation to a proper place.
|
|
||||||
jail.cells = make(map[string]*Cell)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cell returns the existing instance of Cell.
|
// initCell initializes a cell with default JavaScript handlers and user code.
|
||||||
func (jail *Jail) Cell(chatID string) (common.JailCell, error) {
|
func (j *Jail) initCell(cell *Cell) error {
|
||||||
jail.cellsMx.RLock()
|
// Register objects being a bridge between Go and JavaScript.
|
||||||
defer jail.cellsMx.RUnlock()
|
if err := registerWeb3Provider(j, cell); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cell, ok := jail.cells[chatID]
|
if err := registerStatusSignals(cell); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run some initial JS code to provide some global objects.
|
||||||
|
c := []string{
|
||||||
|
j.baseJS,
|
||||||
|
web3Code,
|
||||||
|
web3InstanceCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := cell.Run(strings.Join(c, ";"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAndInitCell creates and initializes a new Cell.
|
||||||
|
func (j *Jail) createAndInitCell(chatID string, code ...string) (*Cell, error) {
|
||||||
|
cell, err := j.createCell(chatID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := j.initCell(cell); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run custom user code
|
||||||
|
for _, js := range code {
|
||||||
|
_, err := cell.Run(js)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAndInitCell creates and initializes new Cell. Additionally,
|
||||||
|
// it creates a `catalog` variable in the VM.
|
||||||
|
// It returns the response as a JSON string.
|
||||||
|
func (j *Jail) CreateAndInitCell(chatID string, code ...string) string {
|
||||||
|
cell, err := j.createAndInitCell(chatID, code...)
|
||||||
|
if err != nil {
|
||||||
|
return newJailErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return j.makeCatalogVariable(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeCatalogVariable provides `catalog` as a global variable.
|
||||||
|
// TODO(divan): this can and should be implemented outside of jail,
|
||||||
|
// on a clojure side. Moving this into separate method to nuke it later
|
||||||
|
// easier.
|
||||||
|
func (j *Jail) makeCatalogVariable(cell *Cell) string {
|
||||||
|
_, err := cell.Run(`var catalog = JSON.stringify(_status_catalog)`)
|
||||||
|
if err != nil {
|
||||||
|
return newJailErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := cell.Get("catalog")
|
||||||
|
if err != nil {
|
||||||
|
return newJailErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newJailResultResponse(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Jail) cell(chatID string) (*Cell, error) {
|
||||||
|
j.cellsMx.RLock()
|
||||||
|
defer j.cellsMx.RUnlock()
|
||||||
|
|
||||||
|
cell, ok := j.cells[chatID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cell[%s] doesn't exist", chatID)
|
return nil, fmt.Errorf("cell '%s' not found", chatID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell, nil
|
return cell, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse creates a new jail cell context, with the given chatID as identifier.
|
// Cell returns a cell by chatID. If it does not exist, error is returned.
|
||||||
// New context executes provided JavaScript code, right after the initialization.
|
// Required by the Backend.
|
||||||
func (jail *Jail) Parse(chatID, js string) string {
|
func (j *Jail) Cell(chatID string) (common.JailCell, error) {
|
||||||
if jail == nil {
|
return j.cell(chatID)
|
||||||
return makeError(ErrInvalidJail.Error())
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cell, err := jail.Cell(chatID)
|
// Execute allows to run arbitrary JS code within a cell.
|
||||||
|
func (j *Jail) Execute(chatID, code string) string {
|
||||||
|
cell, err := j.cell(chatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, mkerr := jail.NewCell(chatID); mkerr != nil {
|
return newJailErrorResponse(err)
|
||||||
return makeError(mkerr.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
cell, _ = jail.Cell(chatID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// init jeth and its handlers
|
value, err := cell.Run(code)
|
||||||
if err = cell.Set("jeth", struct{}{}); err != nil {
|
|
||||||
return makeError(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = registerHandlers(jail, cell, chatID); err != nil {
|
|
||||||
return makeError(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
initJs := jail.baseJSCode + ";"
|
|
||||||
if _, err = cell.Run(initJs); err != nil {
|
|
||||||
return makeError(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
jjs := string(web3JSCode) + `
|
|
||||||
var Web3 = require('web3');
|
|
||||||
var web3 = new Web3(jeth);
|
|
||||||
var Bignumber = require("bignumber.js");
|
|
||||||
function bn(val){
|
|
||||||
return new Bignumber(val);
|
|
||||||
}
|
|
||||||
` + js + "; var catalog = JSON.stringify(_status_catalog);"
|
|
||||||
if _, err = cell.Run(jjs); err != nil {
|
|
||||||
return makeError(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := cell.Get("catalog")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return makeError(err.Error())
|
return newJailErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeResult(res.String(), err)
|
return value.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call executes the `call` function w/i a jail cell context identified by the chatID.
|
// Call executes the `call` function within a cell with chatID.
|
||||||
func (jail *Jail) Call(chatID, this, args string) string {
|
// Returns a string being a valid JS code. In case of a successful result,
|
||||||
cell, err := jail.Cell(chatID)
|
// it's {"result": any}. In case of an error: {"error": "some error"}.
|
||||||
|
//
|
||||||
|
// Call calls commands from `_status_catalog`.
|
||||||
|
// commandPath is an array of properties to retrieve a function.
|
||||||
|
// For instance:
|
||||||
|
// `["prop1", "prop2"]` is translated to `_status_catalog["prop1"]["prop2"]`.
|
||||||
|
func (j *Jail) Call(chatID, commandPath, args string) string {
|
||||||
|
cell, err := j.cell(chatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return makeError(err.Error())
|
return newJailErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := cell.Call("call", nil, this, args)
|
value, err := cell.Call("call", nil, commandPath, args)
|
||||||
|
|
||||||
return makeResult(res.String(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send is a wrapper for executing RPC calls from within Otto VM.
|
|
||||||
// It uses own jail's VM instance instead of cell's one to
|
|
||||||
// increase safety of cell's vm usage.
|
|
||||||
// TODO(divan): investigate if it's possible to do conversions
|
|
||||||
// withouth involving otto code at all.
|
|
||||||
// nolint: errcheck, unparam
|
|
||||||
func (jail *Jail) Send(call otto.FunctionCall) otto.Value {
|
|
||||||
request, err := jail.vm.Call("JSON.stringify", nil, call.Argument(0))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwJSException(err)
|
return newJailErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc := jail.nodeManager.RPCClient()
|
return newJailResultResponse(value)
|
||||||
// TODO(divan): remove this check as soon as jail cells have
|
}
|
||||||
// proper cancellation mechanism implemented.
|
|
||||||
if rpc == nil {
|
|
||||||
throwJSException(fmt.Errorf("Error getting RPC client. Node stopped?"))
|
|
||||||
}
|
|
||||||
response := rpc.CallRaw(request.String())
|
|
||||||
|
|
||||||
// unmarshal response to pass to otto
|
// RPCClient returns an rpc.Client.
|
||||||
var resp interface{}
|
func (j *Jail) RPCClient() *rpc.Client {
|
||||||
err = json.Unmarshal([]byte(response), &resp)
|
if j.rpcClientProvider == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return j.rpcClientProvider.RPCClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRPCCall executes a raw JSON-RPC request.
|
||||||
|
func (j *Jail) sendRPCCall(request string) (interface{}, error) {
|
||||||
|
client := j.RPCClient()
|
||||||
|
if client == nil {
|
||||||
|
return nil, ErrNoRPCClient
|
||||||
|
}
|
||||||
|
|
||||||
|
rawResponse := client.CallRaw(request)
|
||||||
|
|
||||||
|
var response interface{}
|
||||||
|
if err := json.Unmarshal([]byte(rawResponse), &response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJailErrorResponse returns an error.
|
||||||
|
func newJailErrorResponse(err error) string {
|
||||||
|
response := struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}{
|
||||||
|
Error: err.Error(),
|
||||||
|
}
|
||||||
|
|
||||||
|
rawResponse, err := json.Marshal(response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwJSException(fmt.Errorf("Error unmarshalling result: %s", err))
|
return `{"error": "` + err.Error() + `"}`
|
||||||
}
|
|
||||||
respValue, err := jail.vm.ToValue(resp)
|
|
||||||
if err != nil {
|
|
||||||
throwJSException(fmt.Errorf("Error converting result to Otto's value: %s", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return respValue
|
return string(rawResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newErrorResponse(msg string, id interface{}) map[string]interface{} {
|
// newJailResultResponse returns a string that is a valid JavaScript code.
|
||||||
// Bundle the error into a JSON RPC call response
|
// Marshaling is not required as result.String() produces a string
|
||||||
return map[string]interface{}{
|
// that is a valid JavaScript code.
|
||||||
"jsonrpc": "2.0",
|
func newJailResultResponse(result otto.Value) string {
|
||||||
"id": id,
|
return `{"result": ` + result.String() + `}`
|
||||||
"error": map[string]interface{}{
|
|
||||||
"code": -32603, // Internal JSON-RPC Error, see http://www.jsonrpc.org/specification#error_object
|
|
||||||
"message": msg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newErrorResponseOtto(vm *vm.VM, msg string, id interface{}) otto.Value {
|
|
||||||
// TODO(tiabc): Handle errors.
|
|
||||||
errResp, _ := json.Marshal(newErrorResponse(msg, id))
|
|
||||||
errRespVal, _ := vm.Run("(" + string(errResp) + ")")
|
|
||||||
return errRespVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResultResponse(vm *otto.Otto, result interface{}) otto.Value {
|
|
||||||
resp, _ := vm.Object(`({"jsonrpc":"2.0"})`)
|
|
||||||
resp.Set("result", result) // nolint: errcheck
|
|
||||||
|
|
||||||
return resp.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
|
||||||
// Go panic and throw msg as a JavaScript error.
|
|
||||||
// nolint: unparam
|
|
||||||
func throwJSException(msg error) otto.Value {
|
|
||||||
val, err := otto.ToValue(msg.Error())
|
|
||||||
if err != nil {
|
|
||||||
log.Error(fmt.Sprintf("Failed to serialize JavaScript exception %v: %v", msg.Error(), err))
|
|
||||||
}
|
|
||||||
panic(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONError is wrapper around errors, that are sent upwards
|
|
||||||
type JSONError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeError(error string) string {
|
|
||||||
str := JSONError{
|
|
||||||
Error: error,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(&str)
|
|
||||||
return string(outBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeResult(res string, err error) string {
|
|
||||||
var out string
|
|
||||||
if err != nil {
|
|
||||||
out = makeError(err.Error())
|
|
||||||
} else {
|
|
||||||
if "undefined" == res {
|
|
||||||
res = "null"
|
|
||||||
}
|
|
||||||
out = fmt.Sprintf(`{"result": %s}`, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
package jail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
|
"github.com/status-im/status-go/geth/rpc"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testRPCClientProvider struct {
|
||||||
|
rpcClient *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p testRPCClientProvider) RPCClient() *rpc.Client {
|
||||||
|
return p.rpcClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJailTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(JailTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type JailTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
Jail *Jail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) SetupTest() {
|
||||||
|
s.Jail = New(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailCreateCell() {
|
||||||
|
cell, err := s.Jail.CreateCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
s.NotNil(cell)
|
||||||
|
// creating another cell with the same id fails
|
||||||
|
_, err = s.Jail.CreateCell("cell1")
|
||||||
|
s.EqualError(err, "cell with id 'cell1' already exists")
|
||||||
|
|
||||||
|
// create more cells
|
||||||
|
_, err = s.Jail.CreateCell("cell2")
|
||||||
|
s.NoError(err)
|
||||||
|
_, err = s.Jail.CreateCell("cell3")
|
||||||
|
s.NoError(err)
|
||||||
|
s.Len(s.Jail.cells, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailGetCell() {
|
||||||
|
// cell1 does not exist
|
||||||
|
_, err := s.Jail.Cell("cell1")
|
||||||
|
s.EqualError(err, "cell 'cell1' not found")
|
||||||
|
|
||||||
|
// cell 1 exists
|
||||||
|
_, err = s.Jail.CreateCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
cell, err := s.Jail.Cell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
s.NotNil(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailInitCell() {
|
||||||
|
// InitCell on an existing cell.
|
||||||
|
cell, err := s.Jail.createCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
err = s.Jail.initCell(cell)
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// web3 should be available
|
||||||
|
value, err := cell.Run("web3.fromAscii('ethereum')")
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(`0x657468657265756d`, value.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailStop() {
|
||||||
|
_, err := s.Jail.CreateCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
s.Len(s.Jail.cells, 1)
|
||||||
|
|
||||||
|
s.Jail.Stop()
|
||||||
|
|
||||||
|
s.Len(s.Jail.cells, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestJailCall() {
|
||||||
|
cell, err := s.Jail.CreateCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
propsc := make(chan string, 1)
|
||||||
|
argsc := make(chan string, 1)
|
||||||
|
err = cell.Set("call", func(call otto.FunctionCall) otto.Value {
|
||||||
|
propsc <- call.Argument(0).String()
|
||||||
|
argsc <- call.Argument(1).String()
|
||||||
|
|
||||||
|
return otto.UndefinedValue()
|
||||||
|
})
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
result := s.Jail.Call("cell1", `["prop1", "prop2"]`, `arg1`)
|
||||||
|
s.Equal(`["prop1", "prop2"]`, <-propsc)
|
||||||
|
s.Equal(`arg1`, <-argsc)
|
||||||
|
s.Equal(`{"result": undefined}`, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestMakeCatalogVariable() {
|
||||||
|
cell, err := s.Jail.createCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// no `_status_catalog` variable
|
||||||
|
response := s.Jail.makeCatalogVariable(cell)
|
||||||
|
s.Equal(`{"error":"ReferenceError: '_status_catalog' is not defined"}`, response)
|
||||||
|
|
||||||
|
// with `_status_catalog` variable
|
||||||
|
_, err = cell.Run(`var _status_catalog = { test: true }`)
|
||||||
|
s.NoError(err)
|
||||||
|
response = s.Jail.makeCatalogVariable(cell)
|
||||||
|
s.Equal(`{"result": {"test":true}}`, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestCreateAndInitCell() {
|
||||||
|
cell, err := s.Jail.createAndInitCell(
|
||||||
|
"cell1",
|
||||||
|
`var testCreateAndInitCell1 = true`,
|
||||||
|
`var testCreateAndInitCell2 = true`,
|
||||||
|
)
|
||||||
|
s.NoError(err)
|
||||||
|
s.NotNil(cell)
|
||||||
|
|
||||||
|
value, err := cell.Get("testCreateAndInitCell1")
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(`true`, value.String())
|
||||||
|
|
||||||
|
value, err = cell.Get("testCreateAndInitCell2")
|
||||||
|
s.NoError(err)
|
||||||
|
s.Equal(`true`, value.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestPublicCreateAndInitCell() {
|
||||||
|
response := s.Jail.CreateAndInitCell("cell1", `var _status_catalog = { test: true }`)
|
||||||
|
s.Equal(`{"result": {"test":true}}`, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JailTestSuite) TestExecute() {
|
||||||
|
// cell does not exist
|
||||||
|
response := s.Jail.Execute("cell1", "('some string')")
|
||||||
|
s.Equal(`{"error":"cell 'cell1' not found"}`, response)
|
||||||
|
|
||||||
|
_, err := s.Jail.createCell("cell1")
|
||||||
|
s.NoError(err)
|
||||||
|
|
||||||
|
// cell exists
|
||||||
|
response = s.Jail.Execute("cell1", `
|
||||||
|
var obj = { test: true };
|
||||||
|
JSON.stringify(obj);
|
||||||
|
`)
|
||||||
|
s.Equal(`{"test":true}`, response)
|
||||||
|
}
|
|
@ -100,9 +100,14 @@ func (m *NodeManager) startNode(config *params.NodeConfig) (<-chan struct{}, err
|
||||||
m.config = config
|
m.config = config
|
||||||
|
|
||||||
// init RPC client for this node
|
// init RPC client for this node
|
||||||
m.rpcClient, err = rpc.NewClient(m.node, m.config.UpstreamConfig)
|
localRPCClient, errRPC := m.node.Attach()
|
||||||
if err != nil {
|
if errRPC == nil {
|
||||||
log.Error("Init RPC client failed:", "error", err)
|
m.rpcClient, errRPC = rpc.NewClient(localRPCClient, m.config.UpstreamConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errRPC != nil {
|
||||||
|
log.Error("Failed to create an RPC client", "error", errRPC)
|
||||||
|
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
signal.Send(signal.Envelope{
|
signal.Send(signal.Envelope{
|
||||||
Type: signal.EventNodeCrashed,
|
Type: signal.EventNodeCrashed,
|
||||||
|
@ -112,6 +117,7 @@ func (m *NodeManager) startNode(config *params.NodeConfig) (<-chan struct{}, err
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
|
|
||||||
// underlying node is started, every method can use it, we use it immediately
|
// underlying node is started, every method can use it, we use it immediately
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/status-im/status-go/geth/params"
|
"github.com/status-im/status-go/geth/params"
|
||||||
|
|
||||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||||
|
@ -38,21 +37,17 @@ type Client struct {
|
||||||
//
|
//
|
||||||
// Client is safe for concurrent use and will automatically
|
// Client is safe for concurrent use and will automatically
|
||||||
// reconnect to the server if connection is lost.
|
// reconnect to the server if connection is lost.
|
||||||
func NewClient(node *node.Node, upstream params.UpstreamRPCConfig) (*Client, error) {
|
func NewClient(client *gethrpc.Client, upstream params.UpstreamRPCConfig) (*Client, error) {
|
||||||
c := &Client{
|
c := Client{
|
||||||
|
local: client,
|
||||||
handlers: make(map[string]Handler),
|
handlers: make(map[string]Handler),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
c.local, err = node.Attach()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("attach to local node: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if upstream.Enabled {
|
if upstream.Enabled {
|
||||||
c.upstreamEnabled = upstream.Enabled
|
c.upstreamEnabled = upstream.Enabled
|
||||||
c.upstreamURL = upstream.URL
|
c.upstreamURL = upstream.URL
|
||||||
|
|
||||||
c.upstream, err = gethrpc.Dial(c.upstreamURL)
|
c.upstream, err = gethrpc.Dial(c.upstreamURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dial upstream server: %s", err)
|
return nil, fmt.Errorf("dial upstream server: %s", err)
|
||||||
|
@ -61,7 +56,7 @@ func NewClient(node *node.Node, upstream params.UpstreamRPCConfig) (*Client, err
|
||||||
|
|
||||||
c.router = newRouter(c.upstreamEnabled)
|
c.router = newRouter(c.upstreamEnabled)
|
||||||
|
|
||||||
return c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call performs a JSON-RPC call with the given arguments and unmarshals into
|
// Call performs a JSON-RPC call with the given arguments and unmarshals into
|
||||||
|
|
|
@ -320,13 +320,20 @@ func DiscardTransactions(ids *C.char) *C.char {
|
||||||
//InitJail setup initial JavaScript
|
//InitJail setup initial JavaScript
|
||||||
//export InitJail
|
//export InitJail
|
||||||
func InitJail(js *C.char) {
|
func InitJail(js *C.char) {
|
||||||
statusAPI.JailBaseJS(C.GoString(js))
|
statusAPI.SetJailBaseJS(C.GoString(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse creates a new jail cell context and executes provided JavaScript code
|
//CreateAndInitCell creates a new jail cell context and executes provided JavaScript code.
|
||||||
//export Parse
|
//export CreateAndInitCell
|
||||||
func Parse(chatID *C.char, js *C.char) *C.char {
|
func CreateAndInitCell(chatID *C.char, js *C.char) *C.char {
|
||||||
res := statusAPI.JailParse(C.GoString(chatID), C.GoString(js))
|
res := statusAPI.CreateAndInitCell(C.GoString(chatID), C.GoString(js))
|
||||||
|
return C.CString(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ExecuteJS allows to run arbitrary JS code within a cell.
|
||||||
|
//export ExecuteJS
|
||||||
|
func ExecuteJS(chatID *C.char, code *C.char) *C.char {
|
||||||
|
res := statusAPI.JailExecute(C.GoString(chatID), C.GoString(code))
|
||||||
return C.CString(res)
|
return C.CString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
50
lib/utils.go
50
lib/utils.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -114,6 +115,10 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
||||||
"test jailed calls",
|
"test jailed calls",
|
||||||
testJailFunctionCall,
|
testJailFunctionCall,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"test ExecuteJS",
|
||||||
|
testExecuteJS,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -1231,12 +1236,12 @@ func testJailInitInvalid(t *testing.T) bool {
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
InitJail(C.CString(initInvalidCode))
|
InitJail(C.CString(initInvalidCode))
|
||||||
response := C.GoString(Parse(C.CString("CHAT_ID_INIT_TEST"), C.CString(``)))
|
response := C.GoString(CreateAndInitCell(C.CString("CHAT_ID_INIT_INVALID_TEST"), C.CString(``)))
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
expectedResponse := `{"error":"(anonymous): Line 4:3 Unexpected end of input (and 3 more errors)"}`
|
expectedSubstr := `"error":"(anonymous): Line 4:3 Unexpected identifier`
|
||||||
if expectedResponse != response {
|
if !strings.Contains(response, expectedSubstr) {
|
||||||
t.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
t.Errorf("unexpected response, didn't find '%s' in '%s'", expectedSubstr, response)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -1256,11 +1261,10 @@ func testJailParseInvalid(t *testing.T) bool {
|
||||||
var extraFunc = function (x) {
|
var extraFunc = function (x) {
|
||||||
return x * x;
|
return x * x;
|
||||||
`
|
`
|
||||||
response := C.GoString(Parse(C.CString("CHAT_ID_INIT_TEST"), C.CString(extraInvalidCode)))
|
response := C.GoString(CreateAndInitCell(C.CString("CHAT_ID_PARSE_INVALID_TEST"), C.CString(extraInvalidCode)))
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
// expectedResponse := `{"error":"(anonymous): Line 16331:50 Unexpected end of input (and 1 more errors)"}`
|
expectedResponse := `{"error":"(anonymous): Line 4:2 Unexpected end of input (and 1 more errors)"}`
|
||||||
expectedResponse := `{"error":"(anonymous): Line 16354:50 Unexpected end of input (and 1 more errors)"}`
|
|
||||||
if expectedResponse != response {
|
if expectedResponse != response {
|
||||||
t.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
t.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
||||||
return false
|
return false
|
||||||
|
@ -1281,13 +1285,13 @@ func testJailInit(t *testing.T) bool {
|
||||||
return x * x;
|
return x * x;
|
||||||
};
|
};
|
||||||
`
|
`
|
||||||
rawResponse := Parse(C.CString("CHAT_ID_INIT_TEST"), C.CString(extraCode))
|
rawResponse := CreateAndInitCell(C.CString("CHAT_ID_INIT_TEST"), C.CString(extraCode))
|
||||||
parsedResponse := C.GoString(rawResponse)
|
parsedResponse := C.GoString(rawResponse)
|
||||||
|
|
||||||
expectedResponse := `{"result": {"foo":"bar"}}`
|
expectedResponse := `{"result": {"foo":"bar"}}`
|
||||||
|
|
||||||
if !reflect.DeepEqual(expectedResponse, parsedResponse) {
|
if !reflect.DeepEqual(expectedResponse, parsedResponse) {
|
||||||
t.Error("expected output not returned from jail.Parse()")
|
t.Error("expected output not returned from jail.CreateAndInitCell()")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1304,12 +1308,12 @@ func testJailFunctionCall(t *testing.T) bool {
|
||||||
_status_catalog.commands["testCommand"] = function (params) {
|
_status_catalog.commands["testCommand"] = function (params) {
|
||||||
return params.val * params.val;
|
return params.val * params.val;
|
||||||
};`
|
};`
|
||||||
Parse(C.CString("CHAT_ID_CALL_TEST"), C.CString(statusJS))
|
CreateAndInitCell(C.CString("CHAT_ID_CALL_TEST"), C.CString(statusJS))
|
||||||
|
|
||||||
// call with wrong chat id
|
// call with wrong chat id
|
||||||
rawResponse := Call(C.CString("CHAT_IDNON_EXISTENT"), C.CString(""), C.CString(""))
|
rawResponse := Call(C.CString("CHAT_IDNON_EXISTENT"), C.CString(""), C.CString(""))
|
||||||
parsedResponse := C.GoString(rawResponse)
|
parsedResponse := C.GoString(rawResponse)
|
||||||
expectedError := `{"error":"cell[CHAT_IDNON_EXISTENT] doesn't exist"}`
|
expectedError := `{"error":"cell 'CHAT_IDNON_EXISTENT' not found"}`
|
||||||
if parsedResponse != expectedError {
|
if parsedResponse != expectedError {
|
||||||
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, parsedResponse)
|
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, parsedResponse)
|
||||||
return false
|
return false
|
||||||
|
@ -1329,6 +1333,30 @@ func testJailFunctionCall(t *testing.T) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testExecuteJS(t *testing.T) bool {
|
||||||
|
InitJail(C.CString(""))
|
||||||
|
|
||||||
|
// cell does not exist
|
||||||
|
response := C.GoString(ExecuteJS(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString("('some string')")))
|
||||||
|
expectedResponse := `{"error":"cell 'CHAT_ID_EXECUTE_TEST' not found"}`
|
||||||
|
if response != expectedResponse {
|
||||||
|
t.Errorf("expected '%s' but got '%s'", expectedResponse, response)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateAndInitCell(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString(`var obj = { status: true }`))
|
||||||
|
|
||||||
|
// cell does not exist
|
||||||
|
response = C.GoString(ExecuteJS(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString(`JSON.stringify(obj)`)))
|
||||||
|
expectedResponse = `{"status":true}`
|
||||||
|
if response != expectedResponse {
|
||||||
|
t.Errorf("expected '%s' but got '%s'", expectedResponse, response)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func startTestNode(t *testing.T) <-chan struct{} {
|
func startTestNode(t *testing.T) <-chan struct{} {
|
||||||
syncRequired := false
|
syncRequired := false
|
||||||
if _, err := os.Stat(TestDataDir); os.IsNotExist(err) {
|
if _, err := os.Stat(TestDataDir); os.IsNotExist(err) {
|
||||||
|
|
Loading…
Reference in New Issue