2016-09-11 11:44:14 +00:00
|
|
|
package jail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
2016-10-07 14:48:36 +00:00
|
|
|
"time"
|
2016-09-11 11:44:14 +00:00
|
|
|
|
2016-10-07 14:48:36 +00:00
|
|
|
"github.com/eapache/go-resiliency/semaphore"
|
2017-05-02 14:35:37 +00:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
2016-09-11 11:44:14 +00:00
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
|
|
"github.com/status-im/status-go/geth"
|
2017-04-06 19:36:55 +00:00
|
|
|
"github.com/status-im/status-go/static"
|
2016-09-11 11:44:14 +00:00
|
|
|
)
|
|
|
|
|
2016-10-07 14:48:36 +00:00
|
|
|
const (
|
2017-05-03 14:24:48 +00:00
|
|
|
// JailedRuntimeRequestTimeout seconds before jailed request times out
|
2016-10-07 14:48:36 +00:00
|
|
|
JailedRuntimeRequestTimeout = time.Second * 60
|
|
|
|
)
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// errors
|
2016-09-11 11:44:14 +00:00
|
|
|
var (
|
|
|
|
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
|
|
|
)
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// Jail represents jailed environment inside of which we hold
|
|
|
|
// multiple cells. Each cell is separate JavaScript VM.
|
2016-09-11 11:44:14 +00:00
|
|
|
type Jail struct {
|
2016-10-13 18:02:48 +00:00
|
|
|
sync.RWMutex
|
2016-10-21 09:02:38 +00:00
|
|
|
client *rpc.Client // lazy inited on the first call
|
2016-10-07 14:48:36 +00:00
|
|
|
cells map[string]*JailedRuntime // jail supports running many isolated instances of jailed runtime
|
|
|
|
statusJS string
|
|
|
|
requestQueue *geth.JailedRequestQueue
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// JailedRuntime represents single jail cell, which is JavaScript VM.
|
2016-10-07 14:48:36 +00:00
|
|
|
type JailedRuntime struct {
|
|
|
|
id string
|
|
|
|
vm *otto.Otto
|
|
|
|
sem *semaphore.Semaphore
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
var web3JS = static.MustAsset("scripts/web3.js")
|
2016-09-11 11:44:14 +00:00
|
|
|
var jailInstance *Jail
|
|
|
|
var once sync.Once
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// New returns singleton jail environment
|
2016-09-11 11:44:14 +00:00
|
|
|
func New() *Jail {
|
|
|
|
once.Do(func() {
|
|
|
|
jailInstance = &Jail{
|
2016-10-07 14:48:36 +00:00
|
|
|
cells: make(map[string]*JailedRuntime),
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return jailInstance
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// Init allows to setup initial JavaScript to be loaded on each jail.Parse()
|
2016-09-11 11:44:14 +00:00
|
|
|
func Init(js string) *Jail {
|
|
|
|
jailInstance = New() // singleton, we will always get the same reference
|
|
|
|
jailInstance.statusJS = js
|
|
|
|
|
|
|
|
return jailInstance
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// GetInstance returns singleton jail environment instance
|
2016-09-11 11:44:14 +00:00
|
|
|
func GetInstance() *Jail {
|
|
|
|
return New() // singleton, we will always get the same reference
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// NewJailedRuntime initializes and returns jail cell
|
2016-10-07 14:48:36 +00:00
|
|
|
func NewJailedRuntime(id string) *JailedRuntime {
|
|
|
|
return &JailedRuntime{
|
|
|
|
id: id,
|
|
|
|
vm: otto.New(),
|
|
|
|
sem: semaphore.New(1, JailedRuntimeRequestTimeout),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// Parse creates a new jail cell context, with the given chatID as identifier
|
|
|
|
// New context executes provided JavaScript code, right after the initialization.
|
|
|
|
func (jail *Jail) Parse(chatID string, js string) string {
|
|
|
|
var err error
|
2016-09-11 11:44:14 +00:00
|
|
|
if jail == nil {
|
|
|
|
return printError(ErrInvalidJail.Error())
|
|
|
|
}
|
|
|
|
|
2016-10-13 18:02:48 +00:00
|
|
|
jail.Lock()
|
|
|
|
defer jail.Unlock()
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
jail.cells[chatID] = NewJailedRuntime(chatID)
|
|
|
|
vm := jail.cells[chatID].vm
|
2016-10-07 14:48:36 +00:00
|
|
|
|
2016-09-11 11:44:14 +00:00
|
|
|
initJjs := jail.statusJS + ";"
|
2017-05-03 14:24:48 +00:00
|
|
|
if _, err = vm.Run(initJjs); err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
2016-09-11 11:44:14 +00:00
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// init jeth and its handlers
|
|
|
|
if err = vm.Set("jeth", struct{}{}); err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
|
|
|
if err = registerHandlers(jail, vm, chatID); err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
2016-11-28 18:30:25 +00:00
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
jjs := string(web3JS) + `
|
2016-09-11 11:44:14 +00:00
|
|
|
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);"
|
2017-05-03 14:24:48 +00:00
|
|
|
if _, err = vm.Run(jjs); err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
2016-09-11 11:44:14 +00:00
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
res, err := vm.Get("catalog")
|
|
|
|
if err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
2016-09-11 11:44:14 +00:00
|
|
|
|
|
|
|
return printResult(res.String(), err)
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// Call executes given JavaScript function w/i a jail cell context identified by the chatID
|
|
|
|
func (jail *Jail) Call(chatID string, path string, args string) string {
|
2016-10-21 09:02:38 +00:00
|
|
|
_, err := jail.RPCClient()
|
2016-09-11 11:44:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return printError(err.Error())
|
|
|
|
}
|
|
|
|
|
2016-10-13 18:02:48 +00:00
|
|
|
jail.RLock()
|
2017-05-03 14:24:48 +00:00
|
|
|
cell, ok := jail.cells[chatID]
|
2016-09-11 11:44:14 +00:00
|
|
|
if !ok {
|
2016-10-13 18:02:48 +00:00
|
|
|
jail.RUnlock()
|
2017-05-03 14:24:48 +00:00
|
|
|
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatID))
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
2016-10-13 18:02:48 +00:00
|
|
|
jail.RUnlock()
|
2016-09-11 11:44:14 +00:00
|
|
|
|
2016-10-13 18:02:48 +00:00
|
|
|
vm := cell.vm.Copy() // isolate VM to allow concurrent access
|
|
|
|
res, err := vm.Call("call", nil, path, args)
|
2016-09-11 11:44:14 +00:00
|
|
|
|
|
|
|
return printResult(res.String(), err)
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// GetVM returns instance of Otto VM (which is persisted w/i jail cell) by chatID
|
|
|
|
func (jail *Jail) GetVM(chatID string) (*otto.Otto, error) {
|
2016-09-11 11:44:14 +00:00
|
|
|
if jail == nil {
|
|
|
|
return nil, ErrInvalidJail
|
|
|
|
}
|
|
|
|
|
2016-10-13 18:02:48 +00:00
|
|
|
jail.RLock()
|
|
|
|
defer jail.RUnlock()
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
cell, ok := jail.cells[chatID]
|
2016-09-11 11:44:14 +00:00
|
|
|
if !ok {
|
2017-05-03 14:24:48 +00:00
|
|
|
return nil, fmt.Errorf("cell[%s] doesn't exist", chatID)
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
|
2016-10-07 14:48:36 +00:00
|
|
|
return cell.vm, nil
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send will serialize the first argument, send it to the node and returns the response.
|
2017-05-03 14:24:48 +00:00
|
|
|
// nolint: errcheck, unparam
|
|
|
|
func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Value) {
|
2016-10-21 09:02:38 +00:00
|
|
|
client, err := jail.RPCClient()
|
2016-09-11 11:44:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return newErrorResponse(call, -32603, err.Error(), nil)
|
|
|
|
}
|
|
|
|
|
2016-10-07 14:48:36 +00:00
|
|
|
requestQueue, err := jail.RequestQueue()
|
|
|
|
if err != nil {
|
|
|
|
return newErrorResponse(call, -32603, err.Error(), nil)
|
|
|
|
}
|
|
|
|
|
2016-09-11 11:44:14 +00:00
|
|
|
// Remarshal the request into a Go value.
|
|
|
|
JSON, _ := call.Otto.Object("JSON")
|
|
|
|
reqVal, err := JSON.Call("stringify", call.Argument(0))
|
|
|
|
if err != nil {
|
|
|
|
throwJSException(err.Error())
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
rawReq = []byte(reqVal.String())
|
2016-10-07 14:48:36 +00:00
|
|
|
reqs []geth.RPCCall
|
2016-12-18 20:36:17 +00:00
|
|
|
batch bool
|
2016-09-11 11:44:14 +00:00
|
|
|
)
|
|
|
|
if rawReq[0] == '[' {
|
|
|
|
batch = true
|
|
|
|
json.Unmarshal(rawReq, &reqs)
|
|
|
|
} else {
|
|
|
|
batch = false
|
2016-10-07 14:48:36 +00:00
|
|
|
reqs = make([]geth.RPCCall, 1)
|
2016-09-11 11:44:14 +00:00
|
|
|
json.Unmarshal(rawReq, &reqs[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the requests.
|
|
|
|
resps, _ := call.Otto.Object("new Array()")
|
|
|
|
for _, req := range reqs {
|
|
|
|
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
|
2017-05-03 14:24:48 +00:00
|
|
|
resp.Set("id", req.ID)
|
2016-09-11 11:44:14 +00:00
|
|
|
var result json.RawMessage
|
|
|
|
|
2016-10-13 18:02:48 +00:00
|
|
|
// execute directly w/o RPC call to node
|
|
|
|
if req.Method == geth.SendTransactionRequest {
|
|
|
|
txHash, err := requestQueue.ProcessSendTransactionRequest(call.Otto, req)
|
|
|
|
resp.Set("result", txHash.Hex())
|
|
|
|
if err != nil {
|
2017-05-03 14:24:48 +00:00
|
|
|
resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
|
2016-10-13 18:02:48 +00:00
|
|
|
}
|
|
|
|
resps.Call("push", resp)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-10-12 17:51:25 +00:00
|
|
|
// do extra request pre processing (persist message id)
|
|
|
|
// within function semaphore will be acquired and released,
|
|
|
|
// so that no more than one client (per cell) can enter
|
2017-05-03 14:24:48 +00:00
|
|
|
messageID, err := requestQueue.PreProcessRequest(call.Otto, req)
|
2016-10-12 17:51:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return newErrorResponse(call, -32603, err.Error(), nil)
|
|
|
|
}
|
2016-10-07 14:48:36 +00:00
|
|
|
|
2016-09-11 11:44:14 +00:00
|
|
|
errc := make(chan error, 1)
|
|
|
|
errc2 := make(chan error)
|
|
|
|
go func() {
|
|
|
|
errc2 <- <-errc
|
|
|
|
}()
|
|
|
|
errc <- client.Call(&result, req.Method, req.Params...)
|
|
|
|
err = <-errc2
|
|
|
|
|
|
|
|
switch err := err.(type) {
|
|
|
|
case nil:
|
|
|
|
if result == nil {
|
|
|
|
// Special case null because it is decoded as an empty
|
|
|
|
// raw message for some reason.
|
|
|
|
resp.Set("result", otto.NullValue())
|
|
|
|
} else {
|
2017-05-03 14:24:48 +00:00
|
|
|
resultVal, callErr := JSON.Call("parse", string(result))
|
|
|
|
if callErr != nil {
|
|
|
|
resp = newErrorResponse(call, -32603, callErr.Error(), &req.ID).Object()
|
2016-09-11 11:44:14 +00:00
|
|
|
} else {
|
|
|
|
resp.Set("result", resultVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case rpc.Error:
|
|
|
|
resp.Set("error", map[string]interface{}{
|
|
|
|
"code": err.ErrorCode(),
|
|
|
|
"message": err.Error(),
|
|
|
|
})
|
|
|
|
default:
|
2017-05-03 14:24:48 +00:00
|
|
|
resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
resps.Call("push", resp)
|
2016-10-12 17:51:25 +00:00
|
|
|
|
|
|
|
// do extra request post processing (setting back tx context)
|
2017-05-03 14:24:48 +00:00
|
|
|
requestQueue.PostProcessRequest(call.Otto, req, messageID)
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the responses either to the callback (if supplied)
|
|
|
|
// or directly as the return value.
|
|
|
|
if batch {
|
|
|
|
response = resps.Value()
|
|
|
|
} else {
|
|
|
|
response, _ = resps.Get("0")
|
|
|
|
}
|
|
|
|
if fn := call.Argument(1); fn.Class() == "Function" {
|
|
|
|
fn.Call(otto.NullValue(), otto.NullValue(), response)
|
|
|
|
return otto.UndefinedValue()
|
|
|
|
}
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// RPCClient returns RPC client instance, creating it if necessary.
|
|
|
|
// Returned instance is cached, so successive calls receive the same one.
|
|
|
|
// nolint: dupl
|
2016-10-21 09:02:38 +00:00
|
|
|
func (jail *Jail) RPCClient() (*rpc.Client, error) {
|
2016-09-11 11:44:14 +00:00
|
|
|
if jail == nil {
|
|
|
|
return nil, ErrInvalidJail
|
|
|
|
}
|
|
|
|
|
|
|
|
if jail.client != nil {
|
|
|
|
return jail.client, nil
|
|
|
|
}
|
|
|
|
|
2016-12-07 21:07:08 +00:00
|
|
|
nodeManager := geth.NodeManagerInstance()
|
|
|
|
if !nodeManager.NodeInited() {
|
2016-09-11 11:44:14 +00:00
|
|
|
return nil, geth.ErrInvalidGethNode
|
|
|
|
}
|
|
|
|
|
|
|
|
// obtain RPC client from running node
|
2016-10-21 09:02:38 +00:00
|
|
|
client, err := nodeManager.RPCClient()
|
2016-09-11 11:44:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
jail.client = client
|
|
|
|
|
|
|
|
return jail.client, nil
|
|
|
|
}
|
|
|
|
|
2017-05-03 14:24:48 +00:00
|
|
|
// RequestQueue returns request queue instance, creating it if necessary.
|
|
|
|
// Returned instance is cached, so successive calls receive the same one.
|
|
|
|
// nolint: dupl
|
2016-10-07 14:48:36 +00:00
|
|
|
func (jail *Jail) RequestQueue() (*geth.JailedRequestQueue, error) {
|
|
|
|
if jail == nil {
|
|
|
|
return nil, ErrInvalidJail
|
|
|
|
}
|
|
|
|
|
|
|
|
if jail.requestQueue != nil {
|
|
|
|
return jail.requestQueue, nil
|
|
|
|
}
|
|
|
|
|
2016-12-07 21:07:08 +00:00
|
|
|
nodeManager := geth.NodeManagerInstance()
|
|
|
|
if !nodeManager.NodeInited() {
|
2016-10-07 14:48:36 +00:00
|
|
|
return nil, geth.ErrInvalidGethNode
|
|
|
|
}
|
|
|
|
|
|
|
|
requestQueue, err := nodeManager.JailedRequestQueue()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
jail.requestQueue = requestQueue
|
|
|
|
|
|
|
|
return jail.requestQueue, nil
|
|
|
|
}
|
|
|
|
|
2016-09-11 11:44:14 +00:00
|
|
|
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) otto.Value {
|
|
|
|
// Bundle the error into a JSON RPC call response
|
2016-12-01 16:52:37 +00:00
|
|
|
m := map[string]interface{}{"jsonrpc": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
2016-09-11 11:44:14 +00:00
|
|
|
res, _ := json.Marshal(m)
|
|
|
|
val, _ := call.Otto.Run("(" + string(res) + ")")
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
|
2016-12-18 20:36:17 +00:00
|
|
|
func newResultResponse(call otto.FunctionCall, result interface{}) otto.Value {
|
|
|
|
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
|
2017-05-03 14:24:48 +00:00
|
|
|
resp.Set("result", result) // nolint: errcheck
|
2016-12-18 20:36:17 +00:00
|
|
|
|
|
|
|
return resp.Value()
|
|
|
|
}
|
|
|
|
|
2016-09-11 11:44:14 +00:00
|
|
|
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
|
|
|
// Go panic and throw msg as a JavaScript error.
|
|
|
|
func throwJSException(msg interface{}) otto.Value {
|
|
|
|
val, err := otto.ToValue(msg)
|
|
|
|
if err != nil {
|
2017-05-02 14:35:37 +00:00
|
|
|
log.Error(fmt.Sprintf("Failed to serialize JavaScript exception %v: %v", msg, err))
|
2016-09-11 11:44:14 +00:00
|
|
|
}
|
|
|
|
panic(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
func printError(error string) string {
|
|
|
|
str := geth.JSONError{
|
|
|
|
Error: error,
|
|
|
|
}
|
|
|
|
outBytes, _ := json.Marshal(&str)
|
|
|
|
return string(outBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func printResult(res string, err error) string {
|
|
|
|
var out string
|
|
|
|
if err != nil {
|
|
|
|
out = printError(err.Error())
|
|
|
|
} else {
|
|
|
|
if "undefined" == res {
|
|
|
|
res = "null"
|
|
|
|
}
|
|
|
|
out = fmt.Sprintf(`{"result": %s}`, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|