Jailed JSRE: context passing. Fixes #34
This commit is contained in:
parent
f11c6421a1
commit
e31aa3c746
22
geth/node.go
22
geth/node.go
|
@ -50,6 +50,7 @@ var (
|
||||||
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
||||||
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
|
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
|
||||||
ErrInvalidClient = errors.New("RPC client is not properly initialized")
|
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")
|
ErrNodeStartFailure = errors.New("could not create the in-memory node object")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,6 +65,7 @@ type NodeManager struct {
|
||||||
ctx *cli.Context // the CLI context used to start the geth node
|
ctx *cli.Context // the CLI context used to start the geth node
|
||||||
lightEthereum *les.LightEthereum // LES service
|
lightEthereum *les.LightEthereum // LES service
|
||||||
accountManager *accounts.Manager // the account manager attached to the currentNode
|
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()
|
SelectedAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
|
||||||
whisperService *whisper.Whisper // Whisper service
|
whisperService *whisper.Whisper // Whisper service
|
||||||
client *rpc.ClientRestartWrapper // RPC client
|
client *rpc.ClientRestartWrapper // RPC client
|
||||||
|
@ -77,7 +79,9 @@ var (
|
||||||
|
|
||||||
func NewNodeManager(datadir string, rpcport int) *NodeManager {
|
func NewNodeManager(datadir string, rpcport int) *NodeManager {
|
||||||
createOnce.Do(func() {
|
createOnce.Do(func() {
|
||||||
nodeManagerInstance = &NodeManager{}
|
nodeManagerInstance = &NodeManager{
|
||||||
|
jailedRequestQueue: NewJailedRequestsQueue(),
|
||||||
|
}
|
||||||
nodeManagerInstance.MakeNode(datadir, rpcport)
|
nodeManagerInstance.MakeNode(datadir, rpcport)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -277,6 +281,22 @@ func (m *NodeManager) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error)
|
||||||
return m.client, nil
|
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 {
|
func makeDefaultExtra() []byte {
|
||||||
var clientInfo = struct {
|
var clientInfo = struct {
|
||||||
Version uint
|
Version uint
|
||||||
|
|
|
@ -2,9 +2,9 @@ package geth_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/geth"
|
"github.com/status-im/status-go/geth"
|
||||||
)
|
)
|
||||||
|
|
184
geth/txqueue.go
184
geth/txqueue.go
|
@ -8,22 +8,36 @@ extern bool StatusServiceSignalEvent( const char *jsonEvent );
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/cnf/structhash"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/les/status"
|
"github.com/ethereum/go-ethereum/les/status"
|
||||||
|
"github.com/robertkrimen/otto"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EventTransactionQueued = "transaction.queued"
|
EventTransactionQueued = "transaction.queued"
|
||||||
|
SendTransactionRequest = "eth_sendTransaction"
|
||||||
|
MessageIdKey = "message_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
||||||
|
requestCtx := context.Background()
|
||||||
|
requestQueue, err := GetNodeManager().JailedRequestQueue()
|
||||||
|
if err == nil {
|
||||||
|
requestCtx = requestQueue.PopQueuedTxContext(&queuedTx)
|
||||||
|
}
|
||||||
|
|
||||||
event := GethEvent{
|
event := GethEvent{
|
||||||
Type: EventTransactionQueued,
|
Type: EventTransactionQueued,
|
||||||
Event: SendTransactionEvent{
|
Event: SendTransactionEvent{
|
||||||
Id: string(queuedTx.Id),
|
Id: string(queuedTx.Id),
|
||||||
Args: queuedTx.Args,
|
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)
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ type WhisperMessageEvent struct {
|
||||||
type SendTransactionEvent struct {
|
type SendTransactionEvent struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Args status.SendTxArgs `json:"args"`
|
Args status.SendTxArgs `json:"args"`
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompleteTransactionResult struct {
|
type CompleteTransactionResult struct {
|
||||||
|
@ -48,3 +49,9 @@ type GethEvent struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Event interface{} `json:"event"`
|
Event interface{} `json:"event"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RPCCall struct {
|
||||||
|
Id int64
|
||||||
|
Method string
|
||||||
|
Params []interface{}
|
||||||
|
}
|
||||||
|
|
88
jail/jail.go
88
jail/jail.go
|
@ -5,7 +5,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eapache/go-resiliency/semaphore"
|
||||||
"github.com/ethereum/go-ethereum/logger"
|
"github.com/ethereum/go-ethereum/logger"
|
||||||
"github.com/ethereum/go-ethereum/logger/glog"
|
"github.com/ethereum/go-ethereum/logger/glog"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
@ -13,14 +15,25 @@ import (
|
||||||
"github.com/status-im/status-go/geth"
|
"github.com/status-im/status-go/geth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
JailedRuntimeRequestTimeout = time.Second * 60
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Jail struct {
|
type Jail struct {
|
||||||
client *rpc.ClientRestartWrapper // lazy inited on the first call to jail.ClientRestartWrapper()
|
client *rpc.ClientRestartWrapper // lazy inited on the first call to jail.ClientRestartWrapper()
|
||||||
VMs map[string]*otto.Otto
|
cells map[string]*JailedRuntime // jail supports running many isolated instances of jailed runtime
|
||||||
statusJS string
|
statusJS string
|
||||||
|
requestQueue *geth.JailedRequestQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
type JailedRuntime struct {
|
||||||
|
id string
|
||||||
|
vm *otto.Otto
|
||||||
|
sem *semaphore.Semaphore
|
||||||
}
|
}
|
||||||
|
|
||||||
var jailInstance *Jail
|
var jailInstance *Jail
|
||||||
|
@ -29,7 +42,7 @@ var once sync.Once
|
||||||
func New() *Jail {
|
func New() *Jail {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
jailInstance = &Jail{
|
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
|
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 {
|
func (jail *Jail) Parse(chatId string, js string) string {
|
||||||
if jail == nil {
|
if jail == nil {
|
||||||
return printError(ErrInvalidJail.Error())
|
return printError(ErrInvalidJail.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
vm := otto.New()
|
jail.cells[chatId] = NewJailedRuntime(chatId)
|
||||||
|
vm := jail.cells[chatId].vm
|
||||||
|
|
||||||
initJjs := jail.statusJS + ";"
|
initJjs := jail.statusJS + ";"
|
||||||
jail.VMs[chatId] = vm
|
|
||||||
_, err := vm.Run(initJjs)
|
_, err := vm.Run(initJjs)
|
||||||
vm.Set("jeth", struct{}{})
|
vm.Set("jeth", struct{}{})
|
||||||
|
|
||||||
|
@ -83,12 +105,16 @@ func (jail *Jail) Call(chatId string, path string, args string) string {
|
||||||
return printError(err.Error())
|
return printError(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
vm, ok := jail.VMs[chatId]
|
cell, ok := jail.cells[chatId]
|
||||||
if !ok {
|
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)
|
return printResult(res.String(), err)
|
||||||
}
|
}
|
||||||
|
@ -98,18 +124,12 @@ func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
|
||||||
return nil, ErrInvalidJail
|
return nil, ErrInvalidJail
|
||||||
}
|
}
|
||||||
|
|
||||||
vm, ok := jail.VMs[chatId]
|
cell, ok := jail.cells[chatId]
|
||||||
if !ok {
|
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
|
return cell.vm, nil
|
||||||
}
|
|
||||||
|
|
||||||
type jsonrpcCall struct {
|
|
||||||
Id int64
|
|
||||||
Method string
|
|
||||||
Params []interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send will serialize the first argument, send it to the node and returns the response.
|
// Send will serialize the first argument, send it to the node and returns the response.
|
||||||
|
@ -119,6 +139,11 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
return newErrorResponse(call, -32603, err.Error(), nil)
|
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.
|
// Remarshal the request into a Go value.
|
||||||
JSON, _ := call.Otto.Object("JSON")
|
JSON, _ := call.Otto.Object("JSON")
|
||||||
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
||||||
|
@ -127,7 +152,7 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
rawReq = []byte(reqVal.String())
|
rawReq = []byte(reqVal.String())
|
||||||
reqs []jsonrpcCall
|
reqs []geth.RPCCall
|
||||||
batch bool
|
batch bool
|
||||||
)
|
)
|
||||||
if rawReq[0] == '[' {
|
if rawReq[0] == '[' {
|
||||||
|
@ -135,7 +160,7 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
json.Unmarshal(rawReq, &reqs)
|
json.Unmarshal(rawReq, &reqs)
|
||||||
} else {
|
} else {
|
||||||
batch = false
|
batch = false
|
||||||
reqs = make([]jsonrpcCall, 1)
|
reqs = make([]geth.RPCCall, 1)
|
||||||
json.Unmarshal(rawReq, &reqs[0])
|
json.Unmarshal(rawReq, &reqs[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +171,10 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
|
||||||
resp.Set("id", req.Id)
|
resp.Set("id", req.Id)
|
||||||
var result json.RawMessage
|
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()
|
client := clientFactory.Client()
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
errc2 := make(chan error)
|
errc2 := make(chan error)
|
||||||
|
@ -218,6 +247,29 @@ func (jail *Jail) ClientRestartWrapper() (*rpc.ClientRestartWrapper, error) {
|
||||||
return jail.client, nil
|
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 {
|
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) otto.Value {
|
||||||
// Bundle the error into a JSON RPC call response
|
// Bundle the error into a JSON RPC call response
|
||||||
m := map[string]interface{}{"version": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
m := map[string]interface{}{"version": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
package jail_test
|
package jail_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/status-im/status-go/geth"
|
"github.com/status-im/status-go/geth"
|
||||||
"github.com/status-im/status-go/jail"
|
"github.com/status-im/status-go/jail"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TEST_ADDRESS = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
TEST_ADDRESS = "0x89b50b2b26947ccad43accaef76c21d175ad85f4"
|
||||||
|
TEST_ADDRESS_PASSWORD = "asdf"
|
||||||
CHAT_ID_INIT = "CHAT_ID_INIT_TEST"
|
CHAT_ID_INIT = "CHAT_ID_INIT_TEST"
|
||||||
CHAT_ID_CALL = "CHAT_ID_CALL_TEST"
|
CHAT_ID_CALL = "CHAT_ID_CALL_TEST"
|
||||||
|
CHAT_ID_SEND = "CHAT_ID_CALL_SEND"
|
||||||
CHAT_ID_NON_EXISTENT = "CHAT_IDNON_EXISTENT"
|
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) {
|
func TestJailUnInited(t *testing.T) {
|
||||||
|
@ -128,7 +135,7 @@ func TestJailFunctionCall(t *testing.T) {
|
||||||
|
|
||||||
// call with wrong chat id
|
// call with wrong chat id
|
||||||
response := jailInstance.Call(CHAT_ID_NON_EXISTENT, "", "")
|
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 {
|
if response != expectedError {
|
||||||
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, response)
|
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, response)
|
||||||
return
|
return
|
||||||
|
@ -163,11 +170,11 @@ func TestJailRPCSend(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internally (since we replaced `web3.send` with `jail.Send`)
|
||||||
|
// all requests to web3 are forwarded to `jail.Send`
|
||||||
_, err = vm.Run(`
|
_, err = vm.Run(`
|
||||||
var data = {"jsonrpc":"2.0","method":"eth_getBalance","params":["` + TEST_ADDRESS + `", "latest"],"id":1};
|
var balance = web3.eth.getBalance("` + TEST_ADDRESS + `");
|
||||||
var sendResult = web3.currentProvider.send(data)
|
var sendResult = web3.fromWei(balance, "ether")
|
||||||
console.log(JSON.stringify(sendResult))
|
|
||||||
var sendResult = web3.fromWei(sendResult.result, "ether")
|
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot run custom code on VM: %v", err)
|
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)
|
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) {
|
func TestJailMultipleInitSingletonJail(t *testing.T) {
|
||||||
err := geth.PrepareTestNode()
|
err := geth.PrepareTestNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -226,7 +394,7 @@ func TestJailGetVM(t *testing.T) {
|
||||||
|
|
||||||
jailInstance := jail.Init("")
|
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)
|
_, err = jailInstance.GetVM(CHAT_ID_NON_EXISTENT)
|
||||||
if err == nil || err.Error() != expectedError {
|
if err == nil || err.Error() != expectedError {
|
||||||
t.Error("expected error, but call succeeded")
|
t.Error("expected error, but call succeeded")
|
||||||
|
|
|
@ -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}
|
||||||
|
};
|
|
@ -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}
|
||||||
|
};
|
|
@ -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}
|
||||||
|
};
|
|
@ -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}
|
||||||
|
};
|
Loading…
Reference in New Issue