Jailed JSRE: context passing. Fixes #34

This commit is contained in:
Victor Farazdagi 2016-10-07 17:48:36 +03:00
parent f11c6421a1
commit e31aa3c746
10 changed files with 734 additions and 45 deletions

View File

@ -50,6 +50,7 @@ var (
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
ErrInvalidClient = errors.New("RPC client is not properly initialized")
ErrInvalidJailedRequestQueue = errors.New("Jailed request queue is not properly initialized")
ErrNodeStartFailure = errors.New("could not create the in-memory node object")
)
@ -60,14 +61,15 @@ type SelectedExtKey struct {
}
type NodeManager struct {
currentNode *node.Node // currently running geth node
ctx *cli.Context // the CLI context used to start the geth node
lightEthereum *les.LightEthereum // LES service
accountManager *accounts.Manager // the account manager attached to the currentNode
SelectedAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
whisperService *whisper.Whisper // Whisper service
client *rpc.ClientRestartWrapper // RPC client
nodeStarted chan struct{} // channel to wait for node to start
currentNode *node.Node // currently running geth node
ctx *cli.Context // the CLI context used to start the geth node
lightEthereum *les.LightEthereum // LES service
accountManager *accounts.Manager // the account manager attached to the currentNode
jailedRequestQueue *JailedRequestQueue // bridge via which jail notifies node of incoming requests
SelectedAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
whisperService *whisper.Whisper // Whisper service
client *rpc.ClientRestartWrapper // RPC client
nodeStarted chan struct{} // channel to wait for node to start
}
var (
@ -77,7 +79,9 @@ var (
func NewNodeManager(datadir string, rpcport int) *NodeManager {
createOnce.Do(func() {
nodeManagerInstance = &NodeManager{}
nodeManagerInstance = &NodeManager{
jailedRequestQueue: NewJailedRequestsQueue(),
}
nodeManagerInstance.MakeNode(datadir, rpcport)
})
@ -277,6 +281,22 @@ func (m *NodeManager) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error)
return m.client, nil
}
func (m *NodeManager) HasJailedRequestQueue() bool {
return m.jailedRequestQueue != nil
}
func (m *NodeManager) JailedRequestQueue() (*JailedRequestQueue, error) {
if m == nil || !m.HasNode() {
return nil, ErrInvalidGethNode
}
if !m.HasJailedRequestQueue() {
return nil, ErrInvalidJailedRequestQueue
}
return m.jailedRequestQueue, nil
}
func makeDefaultExtra() []byte {
var clientInfo = struct {
Version uint

View File

@ -2,9 +2,9 @@ package geth_test
import (
"os"
"path/filepath"
"testing"
"time"
"path/filepath"
"github.com/status-im/status-go/geth"
)

View File

@ -8,22 +8,36 @@ extern bool StatusServiceSignalEvent( const char *jsonEvent );
import "C"
import (
"context"
"encoding/json"
"fmt"
"bytes"
"github.com/cnf/structhash"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/status"
"github.com/robertkrimen/otto"
)
const (
EventTransactionQueued = "transaction.queued"
SendTransactionRequest = "eth_sendTransaction"
MessageIdKey = "message_id"
)
func onSendTransactionRequest(queuedTx status.QueuedTx) {
requestCtx := context.Background()
requestQueue, err := GetNodeManager().JailedRequestQueue()
if err == nil {
requestCtx = requestQueue.PopQueuedTxContext(&queuedTx)
}
event := GethEvent{
Type: EventTransactionQueued,
Event: SendTransactionEvent{
Id: string(queuedTx.Id),
Args: queuedTx.Args,
Id: string(queuedTx.Id),
Args: queuedTx.Args,
MessageId: fromContext(requestCtx, MessageIdKey),
},
}
@ -41,3 +55,173 @@ func CompleteTransaction(id, password string) (common.Hash, error) {
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
}
func fromContext(ctx context.Context, key string) string {
if ctx == nil {
return ""
}
if messageId, ok := ctx.Value(key).(string); ok {
return messageId
}
return ""
}
type JailedRequest struct {
method string
ctx context.Context
vm *otto.Otto
}
type JailedRequestQueue struct {
requests map[string]*JailedRequest
}
func NewJailedRequestsQueue() *JailedRequestQueue {
return &JailedRequestQueue{
requests: make(map[string]*JailedRequest),
}
}
func (q *JailedRequestQueue) PreProcessRequest(vm *otto.Otto, req RPCCall) {
messageId := currentMessageId(vm.Context())
// save request context for reuse (by request handlers, such as queued transaction signal sender)
ctx := context.Background()
ctx = context.WithValue(ctx, "method", req.Method)
if len(messageId) > 0 {
ctx = context.WithValue(ctx, MessageIdKey, messageId)
}
q.saveRequestContext(vm, ctx, req)
}
func (q *JailedRequestQueue) PostProcessRequest(vm *otto.Otto, req RPCCall) {
// set message id (if present in context)
messageId := currentMessageId(vm.Context())
if len(messageId) > 0 {
vm.Call("addContext", nil, MessageIdKey, messageId)
}
// set extra markers for queued transaction requests
if req.Method == SendTransactionRequest {
vm.Call("addContext", nil, SendTransactionRequest, true)
}
}
func (q *JailedRequestQueue) saveRequestContext(vm *otto.Otto, ctx context.Context, req RPCCall) {
hash := hashFromRPCCall(req)
if len(hash) == 0 { // no need to persist empty hash
return
}
q.requests[hash] = &JailedRequest{
method: req.Method,
ctx: ctx,
vm: vm,
}
}
func (q *JailedRequestQueue) GetQueuedTxContext(queuedTx *status.QueuedTx) context.Context {
hash := hashFromQueuedTx(queuedTx)
req, ok := q.requests[hash]
if ok {
return req.ctx
}
return context.Background()
}
func (q *JailedRequestQueue) PopQueuedTxContext(queuedTx *status.QueuedTx) context.Context {
hash := hashFromQueuedTx(queuedTx)
req, ok := q.requests[hash]
if ok {
delete(q.requests, hash)
return req.ctx
}
return context.Background()
}
// currentMessageId looks for `status.message_id` variable in current JS context
func currentMessageId(ctx otto.Context) string {
if statusObj, ok := ctx.Symbols["status"]; ok {
messageId, err := statusObj.Object().Get("message_id")
if err != nil {
return ""
}
if messageId, err := messageId.ToString(); err == nil {
return messageId
}
}
return ""
}
type HashableSendRequest struct {
method string
from string
to string
value string
data string
}
func hashFromRPCCall(req RPCCall) string {
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
return ""
}
params, ok := req.Params[0].(map[string]interface{})
if !ok {
return ""
}
from, ok := params["from"].(string)
if !ok {
from = ""
}
to, ok := params["to"].(string)
if !ok {
to = ""
}
value, ok := params["value"].(string)
if !ok {
value = ""
}
data, ok := params["data"].(string)
if !ok {
data = ""
}
s := HashableSendRequest{
method: req.Method,
from: from,
to: to,
value: value,
data: data,
}
return fmt.Sprintf("%x", structhash.Sha1(s, 1))
}
func hashFromQueuedTx(queuedTx *status.QueuedTx) string {
value, err := queuedTx.Args.Value.MarshalJSON()
if err != nil {
return ""
}
s := HashableSendRequest{
method: SendTransactionRequest,
from: queuedTx.Args.From.Hex(),
to: queuedTx.Args.To.Hex(),
value: string(bytes.Replace(value, []byte(`"`),[]byte("") , 2)),
data: queuedTx.Args.Data,
}
return fmt.Sprintf("%x", structhash.Sha1(s, 1))
}

View File

@ -35,8 +35,9 @@ type WhisperMessageEvent struct {
}
type SendTransactionEvent struct {
Id string `json:"id"`
Args status.SendTxArgs `json:"args"`
Id string `json:"id"`
Args status.SendTxArgs `json:"args"`
MessageId string `json:"message_id"`
}
type CompleteTransactionResult struct {
@ -48,3 +49,9 @@ type GethEvent struct {
Type string `json:"type"`
Event interface{} `json:"event"`
}
type RPCCall struct {
Id int64
Method string
Params []interface{}
}

View File

@ -5,7 +5,9 @@ import (
"errors"
"fmt"
"sync"
"time"
"github.com/eapache/go-resiliency/semaphore"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc"
@ -13,14 +15,25 @@ import (
"github.com/status-im/status-go/geth"
)
const (
JailedRuntimeRequestTimeout = time.Second * 60
)
var (
ErrInvalidJail = errors.New("jail environment is not properly initialized")
)
type Jail struct {
client *rpc.ClientRestartWrapper // lazy inited on the first call to jail.ClientRestartWrapper()
VMs map[string]*otto.Otto
statusJS string
client *rpc.ClientRestartWrapper // lazy inited on the first call to jail.ClientRestartWrapper()
cells map[string]*JailedRuntime // jail supports running many isolated instances of jailed runtime
statusJS string
requestQueue *geth.JailedRequestQueue
}
type JailedRuntime struct {
id string
vm *otto.Otto
sem *semaphore.Semaphore
}
var jailInstance *Jail
@ -29,7 +42,7 @@ var once sync.Once
func New() *Jail {
once.Do(func() {
jailInstance = &Jail{
VMs: make(map[string]*otto.Otto),
cells: make(map[string]*JailedRuntime),
}
})
@ -47,14 +60,23 @@ func GetInstance() *Jail {
return New() // singleton, we will always get the same reference
}
func NewJailedRuntime(id string) *JailedRuntime {
return &JailedRuntime{
id: id,
vm: otto.New(),
sem: semaphore.New(1, JailedRuntimeRequestTimeout),
}
}
func (jail *Jail) Parse(chatId string, js string) string {
if jail == nil {
return printError(ErrInvalidJail.Error())
}
vm := otto.New()
jail.cells[chatId] = NewJailedRuntime(chatId)
vm := jail.cells[chatId].vm
initJjs := jail.statusJS + ";"
jail.VMs[chatId] = vm
_, err := vm.Run(initJjs)
vm.Set("jeth", struct{}{})
@ -83,12 +105,16 @@ func (jail *Jail) Call(chatId string, path string, args string) string {
return printError(err.Error())
}
vm, ok := jail.VMs[chatId]
cell, ok := jail.cells[chatId]
if !ok {
return printError(fmt.Sprintf("VM[%s] doesn't exist.", chatId))
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatId))
}
res, err := vm.Call("call", nil, path, args)
// serialize requests to VM
cell.sem.Acquire()
defer cell.sem.Release()
res, err := cell.vm.Call("call", nil, path, args)
return printResult(res.String(), err)
}
@ -98,18 +124,12 @@ func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
return nil, ErrInvalidJail
}
vm, ok := jail.VMs[chatId]
cell, ok := jail.cells[chatId]
if !ok {
return nil, fmt.Errorf("VM[%s] doesn't exist.", chatId)
return nil, fmt.Errorf("Cell[%s] doesn't exist.", chatId)
}
return vm, nil
}
type jsonrpcCall struct {
Id int64
Method string
Params []interface{}
return cell.vm, nil
}
// Send will serialize the first argument, send it to the node and returns the response.
@ -119,6 +139,11 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
return newErrorResponse(call, -32603, err.Error(), nil)
}
requestQueue, err := jail.RequestQueue()
if err != nil {
return newErrorResponse(call, -32603, err.Error(), nil)
}
// Remarshal the request into a Go value.
JSON, _ := call.Otto.Object("JSON")
reqVal, err := JSON.Call("stringify", call.Argument(0))
@ -127,7 +152,7 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
}
var (
rawReq = []byte(reqVal.String())
reqs []jsonrpcCall
reqs []geth.RPCCall
batch bool
)
if rawReq[0] == '[' {
@ -135,7 +160,7 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
json.Unmarshal(rawReq, &reqs)
} else {
batch = false
reqs = make([]jsonrpcCall, 1)
reqs = make([]geth.RPCCall, 1)
json.Unmarshal(rawReq, &reqs[0])
}
@ -146,6 +171,10 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
resp.Set("id", req.Id)
var result json.RawMessage
// do extra request pre and post processing (message id persisting, setting tx context)
requestQueue.PreProcessRequest(call.Otto, req)
defer requestQueue.PostProcessRequest(call.Otto, req)
client := clientFactory.Client()
errc := make(chan error, 1)
errc2 := make(chan error)
@ -218,6 +247,29 @@ func (jail *Jail) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error) {
return jail.client, nil
}
func (jail *Jail) RequestQueue() (*geth.JailedRequestQueue, error) {
if jail == nil {
return nil, ErrInvalidJail
}
if jail.requestQueue != nil {
return jail.requestQueue, nil
}
nodeManager := geth.GetNodeManager()
if !nodeManager.HasNode() {
return nil, geth.ErrInvalidGethNode
}
requestQueue, err := nodeManager.JailedRequestQueue()
if err != nil {
return nil, err
}
jail.requestQueue = requestQueue
return jail.requestQueue, nil
}
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) otto.Value {
// Bundle the error into a JSON RPC call response
m := map[string]interface{}{"version": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}

View File

@ -1,20 +1,27 @@
package jail_test
import (
"encoding/json"
"reflect"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/geth"
"github.com/status-im/status-go/jail"
)
const (
TEST_ADDRESS = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
CHAT_ID_INIT = "CHAT_ID_INIT_TEST"
CHAT_ID_CALL = "CHAT_ID_CALL_TEST"
CHAT_ID_NON_EXISTENT = "CHAT_IDNON_EXISTENT"
TEST_ADDRESS = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
TEST_ADDRESS_PASSWORD = "asdf"
CHAT_ID_INIT = "CHAT_ID_INIT_TEST"
CHAT_ID_CALL = "CHAT_ID_CALL_TEST"
CHAT_ID_SEND = "CHAT_ID_CALL_SEND"
CHAT_ID_NON_EXISTENT = "CHAT_IDNON_EXISTENT"
TESTDATA_STATUS_JS = "testdata/status.js"
TESTDATA_STATUS_JS = "testdata/status.js"
TESTDATA_TX_SEND_JS = "testdata/tx-send/"
)
func TestJailUnInited(t *testing.T) {
@ -128,7 +135,7 @@ func TestJailFunctionCall(t *testing.T) {
// call with wrong chat id
response := jailInstance.Call(CHAT_ID_NON_EXISTENT, "", "")
expectedError := `{"error":"VM[CHAT_IDNON_EXISTENT] doesn't exist."}`
expectedError := `{"error":"Cell[CHAT_IDNON_EXISTENT] doesn't exist."}`
if response != expectedError {
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, response)
return
@ -163,11 +170,11 @@ func TestJailRPCSend(t *testing.T) {
return
}
// internally (since we replaced `web3.send` with `jail.Send`)
// all requests to web3 are forwarded to `jail.Send`
_, err = vm.Run(`
var data = {"jsonrpc":"2.0","method":"eth_getBalance","params":["` + TEST_ADDRESS + `", "latest"],"id":1};
var sendResult = web3.currentProvider.send(data)
console.log(JSON.stringify(sendResult))
var sendResult = web3.fromWei(sendResult.result, "ether")
var balance = web3.eth.getBalance("` + TEST_ADDRESS + `");
var sendResult = web3.fromWei(balance, "ether")
`)
if err != nil {
t.Errorf("cannot run custom code on VM: %v", err)
@ -194,6 +201,167 @@ func TestJailRPCSend(t *testing.T) {
t.Logf("Balance of %.2f ETH found on '%s' account", balance, TEST_ADDRESS)
}
func TestJailSendQueuedTransaction(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
}
txParams := `{
"from": "` + TEST_ADDRESS + `",
"to": "0xf82da7547534045b4e00442bc89e16186cf8c272",
"value": "0.000001"
}`
transactionCompletedSuccessfully := make(chan bool)
// replace transaction notification handler
var txHash = common.Hash{}
requireMessageId := false
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope geth.GethEvent
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
if envelope.Type == geth.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
messageId, ok := event["message_id"].(string)
if !ok {
t.Error("Message id is required, but not found")
return
}
if requireMessageId {
if len(messageId) == 0 {
t.Error("Message id is required, but not provided")
return
}
} else {
if len(messageId) != 0 {
t.Error("Message id is not required, but provided")
return
}
}
t.Logf("Transaction queued (will be completed in 5 secs): {id: %s}\n", event["id"].(string))
time.Sleep(5 * time.Second)
if txHash, err = geth.CompleteTransaction(event["id"].(string), TEST_ADDRESS_PASSWORD); err != nil {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
return
}
t.Logf("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
transactionCompletedSuccessfully <- true // so that timeout is aborted
}
})
type cmd struct {
command string
params string
expectedResponse string
}
tests := []struct {
name string
file string
requireMessageId bool
commands []cmd
}{
{
// no context or message id
name: "Case 1: no message id or context in inited JS",
file: "no-message-id-or-context.js",
requireMessageId: false,
commands: []cmd{
{
`["commands", "send"]`,
txParams,
`{"result": {"transaction-hash":"TX_HASH"}}`,
},
{
`["commands", "getBalance"]`,
`{"address": "` + TEST_ADDRESS + `"}`,
`{"result": {"balance":42}}`,
},
},
},
{
// context is present in inited JS (but no message id is there)
name: "Case 2: context is present in inited JS (but no message id is there)",
file: "context-no-message-id.js",
requireMessageId: false,
commands: []cmd{
{
`["commands", "send"]`,
txParams,
`{"result": {"context":{"` + geth.SendTransactionRequest + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
},
{
`["commands", "getBalance"]`,
`{"address": "` + TEST_ADDRESS + `"}`,
`{"result": {"context":{},"result":{"balance":42}}}`, // note emtpy (but present) context!
},
},
},
{
// message id is present in inited JS, but no context is there
name: "Case 3: message id is present, context is not present",
file: "message-id-no-context.js",
requireMessageId: true,
commands: []cmd{
{
`["commands", "send"]`,
txParams,
`{"result": {"transaction-hash":"TX_HASH"}}`,
},
{
`["commands", "getBalance"]`,
`{"address": "` + TEST_ADDRESS + `"}`,
`{"result": {"balance":42}}`, // note emtpy context!
},
},
},
{
// both message id and context are present in inited JS (this UC is what we normally expect to see)
name: "Case 4: both message id and context are present",
file: "tx-send.js",
requireMessageId: true,
commands: []cmd{
{
`["commands", "send"]`,
txParams,
`{"result": {"context":{"eth_sendTransaction":true,"message_id":"foobar"},"result":{"transaction-hash":"TX_HASH"}}}`,
},
{
`["commands", "getBalance"]`,
`{"address": "` + TEST_ADDRESS + `"}`,
`{"result": {"context":{"message_id":"foobar"},"result":{"balance":42}}}`, // message id in context!
},
},
},
}
//var jailInstance *jail.Jail
for _, test := range tests {
jailInstance := jail.Init(geth.LoadFromFile(TESTDATA_TX_SEND_JS + test.file))
geth.PanicAfter(20*time.Second, transactionCompletedSuccessfully, test.name)
jailInstance.Parse(CHAT_ID_SEND, ``)
requireMessageId = test.requireMessageId
for _, cmd := range test.commands {
t.Logf("%s: %s", test.name, cmd.command)
response := jailInstance.Call(CHAT_ID_SEND, cmd.command, cmd.params)
expectedResponse := strings.Replace(cmd.expectedResponse, "TX_HASH", txHash.Hex(), 1)
if response != expectedResponse {
t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response)
return
}
}
}
}
func TestJailMultipleInitSingletonJail(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
@ -226,7 +394,7 @@ func TestJailGetVM(t *testing.T) {
jailInstance := jail.Init("")
expectedError := `VM[` + CHAT_ID_NON_EXISTENT + `] doesn't exist.`
expectedError := `Cell[` + CHAT_ID_NON_EXISTENT + `] doesn't exist.`
_, err = jailInstance.GetVM(CHAT_ID_NON_EXISTENT)
if err == nil || err.Error() != expectedError {
t.Error("expected error, but call succeeded")

View File

@ -0,0 +1,66 @@
var _status_catalog = {
commands: {},
responses: {}
};
var context = {};
function addContext(key, value) {
context[key] = value;
}
function call(pathStr, paramsStr) {
var params = JSON.parse(paramsStr),
path = JSON.parse(pathStr),
fn, res;
context = {};
fn = path.reduce(function(catalog, name) {
if (catalog && catalog[name]) {
return catalog[name];
}
}, _status_catalog);
if (!fn) {
return null;
}
// while fn wll be executed context will be populated
// by addContext calls from status-go
callResult = fn(params);
res = {
result: callResult,
// so context could contain
// {transaction-sent: true}
context: context
};
return JSON.stringify(res);
}
function sendTransaction(params) {
var data = {
from: params.from,
to: params.to,
value: web3.toWei(params.value, "ether")
};
// Blocking call, it will return when transaction is complete.
// While call is executing, status-go will call up the application,
// allowing it to validate and complete transaction
var hash = web3.eth.sendTransaction(data);
return {"transaction-hash": hash};
}
_status_catalog.commands['send'] = sendTransaction;
_status_catalog.commands['getBalance'] = function (params) {
var balance = web3.eth.getBalance(params.address);
balance = web3.fromWei(balance, "ether")
if (balance < 90) {
console.log("Unexpected balance (<90): ", balance)
}
// used in tx tests, to check that non-context, non-message-id requests work too,
// so actual balance is not important
return {"balance": 42}
};

View File

@ -0,0 +1,64 @@
// jail.Send() expects to find the current message id in `status.message_id`
// (if not found message id will not be injected, and operation will proceed)
var status = {
message_id: '42'
};
var _status_catalog = {
commands: {},
responses: {}
};
function call(pathStr, paramsStr) {
var params = JSON.parse(paramsStr),
path = JSON.parse(pathStr),
fn, res;
fn = path.reduce(function(catalog, name) {
if (catalog && catalog[name]) {
return catalog[name];
}
}, _status_catalog);
if (!fn) {
return null;
}
// while fn wll be executed context will be populated
// by addContext calls from status-go
res = fn(params);
return JSON.stringify(res);
}
function sendTransaction(params) {
var data = {
from: params.from,
to: params.to,
value: web3.toWei(params.value, "ether")
};
// message_id allows you to distinguish between !send invocations
// (when you receive transaction queued event, message_id will be
// attached along the queued transaction id)
status.message_id = 'foobar';
// Blocking call, it will return when transaction is complete.
// While call is executing, status-go will call up the application,
// allowing it to validate and complete transaction
var hash = web3.eth.sendTransaction(data);
return {"transaction-hash": hash};
}
_status_catalog.commands['send'] = sendTransaction;
_status_catalog.commands['getBalance'] = function (params) {
var balance = web3.eth.getBalance(params.address);
balance = web3.fromWei(balance, "ether");
if (balance < 90) {
console.log("Unexpected balance (<90): ", balance)
}
// used in tx tests, to check that non-context, non-message-is requests work too
// so actual balance is not important
return {"balance": 42}
};

View File

@ -0,0 +1,51 @@
var _status_catalog = {
commands: {},
responses: {}
};
function call(pathStr, paramsStr) {
var params = JSON.parse(paramsStr),
path = JSON.parse(pathStr),
fn, res;
fn = path.reduce(function(catalog, name) {
if (catalog && catalog[name]) {
return catalog[name];
}
}, _status_catalog);
if (!fn) {
return null;
}
res = fn(params);
return JSON.stringify(res);
}
function sendTransaction(params) {
var data = {
from: params.from,
to: params.to,
value: web3.toWei(params.value, "ether")
};
// Blocking call, it will return when transaction is complete.
// While call is executing, status-go will call up the application,
// allowing it to validate and complete transaction
var hash = web3.eth.sendTransaction(data);
return {"transaction-hash": hash};
}
_status_catalog.commands['send'] = sendTransaction;
_status_catalog.commands['getBalance'] = function (params) {
var balance = web3.eth.getBalance(params.address);
balance = web3.fromWei(balance, "ether")
if (balance < 90) {
console.log("Unexpected balance (<90): ", balance)
}
// used in tx tests, to check that non-context, non-message-is requests work too
// so actual balance is not important
return {"balance": 42}
};

77
jail/testdata/tx-send/tx-send.js vendored Normal file
View File

@ -0,0 +1,77 @@
// jail.Send() expects to find the current message id in `status.message_id`
// (if not found message id will not be injected, and operation will proceed)
var status = {
message_id: '42' // global message id, gets replaced in sendTransaction (or any other method)
};
var _status_catalog = {
commands: {},
responses: {}
};
var context = {};
function addContext(key, value) { // this function is expected to be present, as status-go uses it to set context
context[key] = value;
}
function call(pathStr, paramsStr) {
var params = JSON.parse(paramsStr),
path = JSON.parse(pathStr),
fn, res;
context = {};
fn = path.reduce(function(catalog, name) {
if (catalog && catalog[name]) {
return catalog[name];
}
}, _status_catalog);
if (!fn) {
return null;
}
// while fn wll be executed context will be populated
// by addContext calls from status-go
callResult = fn(params);
res = {
result: callResult,
// so context could contain {eth_transactionSend: true}
// additionally, context gets `message_id` as well
context: context
};
return JSON.stringify(res);
}
function sendTransaction(params) {
var data = {
from: params.from,
to: params.to,
value: web3.toWei(params.value, "ether")
};
// message_id allows you to distinguish between !send invocations
// (when you receive transaction queued event, message_id will be
// attached along the queued transaction id)
status.message_id = 'foobar';
// Blocking call, it will return when transaction is complete.
// While call is executing, status-go will call up the application,
// allowing it to validate and complete transaction
var hash = web3.eth.sendTransaction(data);
return {"transaction-hash": hash};
}
_status_catalog.commands['send'] = sendTransaction;
_status_catalog.commands['getBalance'] = function (params) {
var balance = web3.eth.getBalance(params.address);
balance = web3.fromWei(balance, "ether");
if (balance < 90) {
console.log("Unexpected balance (<90): ", balance)
}
// used in tx tests, to check that non-context, non-message-is requests work too
// so actual balance is not important
return {"balance": 42}
};