422 lines
12 KiB
Go
422 lines
12 KiB
Go
package jail
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/status-im/status-go/jail"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/signal"
|
|
e2e "github.com/status-im/status-go/t/e2e"
|
|
. "github.com/status-im/status-go/t/utils"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
func TestJailRPCTestSuite(t *testing.T) {
|
|
suite.Run(t, new(JailRPCTestSuite))
|
|
}
|
|
|
|
type JailRPCTestSuite struct {
|
|
e2e.BackendTestSuite
|
|
|
|
jail jail.Manager
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) SetupTest() {
|
|
s.BackendTestSuite.SetupTest()
|
|
s.jail = s.Backend.JailManager()
|
|
s.NotNil(s.jail)
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) TestJailRPCSend() {
|
|
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
|
|
|
s.StartTestBackend()
|
|
defer s.StopTestBackend()
|
|
|
|
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
|
|
|
// load Status JS and add test command to it
|
|
s.jail.SetBaseJS(baseStatusJSCode)
|
|
s.jail.CreateAndInitCell(testChatID)
|
|
|
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
|
cell, err := s.jail.Cell(testChatID)
|
|
s.NoError(err)
|
|
s.NotNil(cell)
|
|
|
|
// internally (since we replaced `web3.send` with `jail.Send`)
|
|
// all requests to web3 are forwarded to `jail.Send`
|
|
_, err = cell.Run(`
|
|
var balance = web3.eth.getBalance("` + TestConfig.Account1.Address + `");
|
|
var sendResult = web3.fromWei(balance, "ether")
|
|
`)
|
|
s.NoError(err)
|
|
|
|
value, err := cell.Get("sendResult")
|
|
s.NoError(err, "cannot obtain result of balance check operation")
|
|
|
|
balance, err := value.Value().ToFloat()
|
|
s.NoError(err)
|
|
|
|
s.T().Logf("Balance of %.2f ETH found on '%s' account", balance, TestConfig.Account1.Address)
|
|
s.False(balance < 1, "wrong balance (there should be lots of test Ether on that account)")
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) TestIsConnected() {
|
|
s.StartTestBackend()
|
|
defer s.StopTestBackend()
|
|
|
|
s.jail.CreateAndInitCell(testChatID)
|
|
|
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
|
cell, err := s.jail.Cell(testChatID)
|
|
s.NoError(err)
|
|
|
|
_, err = cell.Run(`
|
|
var responseValue = web3.isConnected();
|
|
responseValue = JSON.stringify(responseValue);
|
|
`)
|
|
s.NoError(err)
|
|
|
|
responseValue, err := cell.Get("responseValue")
|
|
s.NoError(err, "cannot obtain result of isConnected()")
|
|
|
|
response, err := responseValue.Value().ToBoolean()
|
|
s.NoError(err, "cannot parse result")
|
|
s.True(response)
|
|
}
|
|
|
|
// regression test: eth_getTransactionReceipt with invalid transaction hash should return "result":null.
|
|
func (s *JailRPCTestSuite) TestRegressionGetTransactionReceipt() {
|
|
s.StartTestBackend()
|
|
defer s.StopTestBackend()
|
|
|
|
rpcClient := s.Backend.StatusNode().RPCClient()
|
|
s.NotNil(rpcClient)
|
|
|
|
// note: transaction hash is assumed to be invalid
|
|
got := rpcClient.CallRaw(`{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xbbebf28d0a3a3cbb38e6053a5b21f08f82c62b0c145a17b1c4313cac3f68ae7c"],"id":7}`)
|
|
expected := `{"jsonrpc":"2.0","id":7,"result":null}`
|
|
s.Equal(expected, got)
|
|
}
|
|
func (s *JailRPCTestSuite) TestContractDeploymentLES() {
|
|
s.testContractDeployment(false)
|
|
}
|
|
func (s *JailRPCTestSuite) TestContractDeploymentRPC() {
|
|
s.testContractDeployment(true)
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) testContractDeployment(upstream bool) {
|
|
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
|
if upstream && GetNetworkID() == params.StatusChainNetworkID {
|
|
// only testing RPC on rinkeby
|
|
s.T().Skip()
|
|
}
|
|
|
|
testContractMining := true
|
|
if GetNetworkID() == params.StatusChainNetworkID {
|
|
// we don't do mining on our chain
|
|
testContractMining = false
|
|
}
|
|
|
|
s.StartTestBackend(setUpstreamOption(s.T(), upstream))
|
|
defer s.StopTestBackend()
|
|
|
|
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
|
|
|
if !upstream {
|
|
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
|
}
|
|
|
|
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
|
s.jail.CreateAndInitCell(testChatID)
|
|
|
|
cell, err := s.jail.Cell(testChatID)
|
|
s.NoError(err)
|
|
|
|
completeQueuedTransaction := make(chan struct{})
|
|
|
|
var txHash gethcommon.Hash
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope signal.Envelope
|
|
unmarshalErr := json.Unmarshal([]byte(jsonEvent), &envelope)
|
|
s.NoError(unmarshalErr, "cannot unmarshal JSON: %s", jsonEvent)
|
|
|
|
if envelope.Type == signal.EventSignRequestAdded {
|
|
event := envelope.Event.(map[string]interface{})
|
|
s.T().Logf("transaction queued and will be completed shortly, id: %v", event["id"])
|
|
|
|
txID := event["id"].(string)
|
|
result := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
|
txHash.SetBytes(result.Response.Bytes())
|
|
if s.NoError(result.Error, event["id"]) {
|
|
s.T().Logf("contract transaction complete, URL: %s", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
|
}
|
|
|
|
close(completeQueuedTransaction)
|
|
}
|
|
})
|
|
|
|
_, err = cell.Run(`
|
|
var responseValue = null;
|
|
var errorValue = null;
|
|
var wasMined = false;
|
|
var wasSent = false;
|
|
var testContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"int256"}],"name":"double","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"}]);
|
|
var test = testContract.new(
|
|
{
|
|
from: '` + TestConfig.Account1.Address + `',
|
|
data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029',
|
|
gas: '` + strconv.Itoa(params.DefaultGas) + `'
|
|
}, function (e, contract) {
|
|
// NOTE: The callback will fire twice!
|
|
if (e) {
|
|
errorValue = e;
|
|
return;
|
|
}
|
|
// Once the contract has the transactionHash property set and once its deployed on an address.
|
|
if (!contract.address) {
|
|
responseValue = contract.transactionHash;
|
|
wasSent = true;
|
|
}
|
|
if (contract.address) {
|
|
wasMined = true;
|
|
}
|
|
})
|
|
`)
|
|
s.NoError(err)
|
|
|
|
select {
|
|
case <-completeQueuedTransaction:
|
|
case <-time.After(time.Minute):
|
|
s.FailNow("test timed out")
|
|
}
|
|
|
|
s.waitForBoolValue(cell, "wasSent", true, 30*time.Second, "timeout while waiting for the contract tx to be sent")
|
|
|
|
errorValue, err := cell.Get("errorValue")
|
|
s.NoError(err)
|
|
s.Equal("null", errorValue.Value().String())
|
|
|
|
responseValue, err := cell.Get("responseValue")
|
|
s.NoError(err)
|
|
|
|
response, err := responseValue.Value().ToString()
|
|
s.NoError(err)
|
|
|
|
expectedResponse := txHash.Hex()
|
|
s.Equal(expectedResponse, response)
|
|
|
|
if testContractMining {
|
|
s.waitForBoolValue(cell, "wasMined", true, 3*time.Minute, "timeout while waiting for the contract to be mined")
|
|
}
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) waitForBoolValue(cell jail.JSCell, name string, expected bool, timeout time.Duration, msg string) {
|
|
deadline := time.Now().Add(timeout)
|
|
|
|
s.T().Logf("waiting for the variable '%s' to be '%v' with timeout of '%v'", name, expected, timeout)
|
|
|
|
for time.Now().Before(deadline) {
|
|
boolValue, err := cell.Get(name)
|
|
s.NoError(err)
|
|
actualValue, err := boolValue.Value().ToBoolean()
|
|
s.NoError(err)
|
|
if actualValue == expected {
|
|
return
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
s.Fail(msg)
|
|
}
|
|
|
|
func (s *JailRPCTestSuite) TestJailVMPersistence() {
|
|
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
|
|
|
s.StartTestBackend()
|
|
defer s.StopTestBackend()
|
|
|
|
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
|
|
|
// log into account from which transactions will be sent
|
|
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
|
s.NoError(err, "cannot select account: %v", TestConfig.Account1.Address)
|
|
|
|
// there are two `sendTestTx` calls in `testCases`
|
|
var wgTransctions sync.WaitGroup
|
|
wgTransctions.Add(2)
|
|
|
|
type testCase struct {
|
|
command string
|
|
params string
|
|
validator func(response string) error
|
|
}
|
|
var testCases = []testCase{
|
|
{
|
|
`["sendTestTx"]`,
|
|
`{"amount": "0.000001", "from": "` + TestConfig.Account1.Address + `"}`,
|
|
func(response string) error {
|
|
if strings.Contains(response, "error") {
|
|
return fmt.Errorf("unexpected response: %v", response)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
`["sendTestTx"]`,
|
|
`{"amount": "0.000002", "from": "` + TestConfig.Account1.Address + `"}`,
|
|
func(response string) error {
|
|
if strings.Contains(response, "error") {
|
|
return fmt.Errorf("unexpected response: %v", response)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
`["ping"]`,
|
|
`{"pong": "Ping1", "amount": 0.42}`,
|
|
func(response string) error {
|
|
expectedResponse := `{"result":"Ping1"}`
|
|
if response != expectedResponse {
|
|
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
`["ping"]`,
|
|
`{"pong": "Ping2", "amount": 0.42}`,
|
|
func(response string) error {
|
|
expectedResponse := `{"result":"Ping2"}`
|
|
if response != expectedResponse {
|
|
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
jail := s.Backend.JailManager()
|
|
jail.SetBaseJS(baseStatusJSCode)
|
|
|
|
parseResult := jail.CreateAndInitCell(testChatID, `
|
|
var total = 0;
|
|
_status_catalog['ping'] = function(params) {
|
|
total += Number(params.amount);
|
|
return params.pong;
|
|
}
|
|
|
|
_status_catalog['sendTestTx'] = function(params) {
|
|
var amount = params.amount;
|
|
var transaction = {
|
|
"from": params.from,
|
|
"to": "`+TestConfig.Account2.Address+`",
|
|
"value": web3.toWei(amount, "ether")
|
|
};
|
|
web3.eth.sendTransaction(transaction, function (error, result) {
|
|
if(!error) {
|
|
total += Number(amount);
|
|
_done();
|
|
}
|
|
});
|
|
}
|
|
|
|
_status_catalog;
|
|
`)
|
|
s.NotContains(parseResult, "error", "further will fail if initial parsing failed")
|
|
|
|
cell, err := jail.Cell(testChatID)
|
|
s.NoError(err)
|
|
|
|
// create a bridge between JS code and Go
|
|
// to notify when the tx callback is called
|
|
err = cell.Set("_done", func(call otto.FunctionCall) otto.Value {
|
|
wgTransctions.Done()
|
|
return otto.UndefinedValue()
|
|
})
|
|
s.NoError(err)
|
|
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope signal.Envelope
|
|
if e := json.Unmarshal([]byte(jsonEvent), &envelope); e != nil {
|
|
s.T().Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == signal.EventSignRequestAdded {
|
|
event := envelope.Event.(map[string]interface{})
|
|
s.T().Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
|
|
|
var txHash gethcommon.Hash
|
|
txID := event["id"].(string)
|
|
result := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
|
s.NoError(result.Error, "cannot complete queued transaction[%v]: %v", event["id"], result.Error)
|
|
|
|
txHash.SetBytes(result.Response.Bytes())
|
|
s.T().Logf("Transaction complete: %s", txHash.Hex())
|
|
}
|
|
})
|
|
|
|
// run commands concurrently
|
|
var wg sync.WaitGroup
|
|
for _, tc := range testCases {
|
|
wg.Add(1)
|
|
go func(tc testCase) {
|
|
defer wg.Done() // ensure we don't forget it
|
|
|
|
s.T().Logf("CALL START: %v %v", tc.command, tc.params)
|
|
response := jail.Call(testChatID, tc.command, tc.params)
|
|
if e := tc.validator(response); e != nil {
|
|
s.T().Errorf("failed test validation: %v, err: %v", tc.command, e)
|
|
}
|
|
s.T().Logf("CALL END: %v %v", tc.command, tc.params)
|
|
}(tc)
|
|
}
|
|
|
|
finishTestCases := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
wgTransctions.Wait()
|
|
close(finishTestCases)
|
|
}()
|
|
|
|
select {
|
|
case <-finishTestCases:
|
|
case <-time.After(time.Minute):
|
|
s.FailNow("some tests failed to finish in time")
|
|
}
|
|
|
|
// Validate total.
|
|
totalOtto, err := cell.Get("total")
|
|
s.NoError(err)
|
|
|
|
total, err := totalOtto.Value().ToFloat()
|
|
s.NoError(err)
|
|
|
|
s.T().Log(total)
|
|
s.InDelta(0.840003, total, 0.0000001)
|
|
}
|
|
|
|
func setUpstreamOption(t *testing.T, upstream bool) e2e.TestNodeOption {
|
|
return func(config *params.NodeConfig) {
|
|
if upstream {
|
|
networkURL, err := GetRemoteURL()
|
|
assert.NoError(t, err)
|
|
config.UpstreamConfig.Enabled = true
|
|
config.UpstreamConfig.URL = networkURL
|
|
}
|
|
}
|
|
}
|