javascript_runtime.go 5.27 KB
Newer Older
zelig's avatar
zelig committed
1
package main
obscuren's avatar
obscuren committed
2 3 4 5

import (
	"fmt"
	"github.com/ethereum/eth-go"
obscuren's avatar
obscuren committed
6
	"github.com/ethereum/eth-go/ethchain"
zelig's avatar
zelig committed
7
	"github.com/ethereum/eth-go/ethlog"
obscuren's avatar
obscuren committed
8
	"github.com/ethereum/eth-go/ethpub"
obscuren's avatar
obscuren committed
9
	"github.com/ethereum/eth-go/ethutil"
obscuren's avatar
obscuren committed
10
	"github.com/ethereum/go-ethereum/utils"
11
	"github.com/obscuren/otto"
12 13
	"io/ioutil"
	"os"
14
	"path"
15
	"path/filepath"
obscuren's avatar
obscuren committed
16 17
)

zelig's avatar
zelig committed
18 19
var jsrelogger = ethlog.NewLogger("JSRE")

obscuren's avatar
obscuren committed
20
type JSRE struct {
obscuren's avatar
obscuren committed
21 22 23 24 25 26 27 28 29
	ethereum *eth.Ethereum
	vm       *otto.Otto
	lib      *ethpub.PEthereum

	blockChan  chan ethutil.React
	changeChan chan ethutil.React
	quitChan   chan bool

	objectCb map[string][]otto.Value
obscuren's avatar
obscuren committed
30 31
}

32 33 34 35 36
func (jsre *JSRE) LoadExtFile(path string) {
	result, err := ioutil.ReadFile(path)
	if err == nil {
		jsre.vm.Run(result)
	} else {
zelig's avatar
zelig committed
37
		jsrelogger.Debugln("Could not load file:", path)
38 39 40 41 42 43 44 45
	}
}

func (jsre *JSRE) LoadIntFile(file string) {
	assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal", "assets", "ext")
	jsre.LoadExtFile(path.Join(assetPath, file))
}

obscuren's avatar
obscuren committed
46
func NewJSRE(ethereum *eth.Ethereum) *JSRE {
obscuren's avatar
obscuren committed
47 48 49 50 51 52 53 54 55 56
	re := &JSRE{
		ethereum,
		otto.New(),
		ethpub.NewPEthereum(ethereum),
		make(chan ethutil.React, 1),
		make(chan ethutil.React, 1),
		make(chan bool),
		make(map[string][]otto.Value),
	}

obscuren's avatar
obscuren committed
57 58 59
	// Init the JS lib
	re.vm.Run(jsLib)

60 61 62 63
	// Load extra javascript files
	re.LoadIntFile("string.js")
	re.LoadIntFile("big.js")

obscuren's avatar
obscuren committed
64 65
	// We have to make sure that, whoever calls this, calls "Stop"
	go re.mainLoop()
obscuren's avatar
obscuren committed
66 67

	re.Bind("eth", &JSEthereum{re.lib, re.vm})
obscuren's avatar
obscuren committed
68

obscuren's avatar
obscuren committed
69
	re.initStdFuncs()
obscuren's avatar
obscuren committed
70

zelig's avatar
zelig committed
71 72
	jsrelogger.Infoln("started")

obscuren's avatar
obscuren committed
73 74
	return re
}
obscuren's avatar
obscuren committed
75

obscuren's avatar
obscuren committed
76 77 78
func (self *JSRE) Bind(name string, v interface{}) {
	self.vm.Set(name, v)
}
obscuren's avatar
obscuren committed
79

obscuren's avatar
obscuren committed
80 81
func (self *JSRE) Run(code string) (otto.Value, error) {
	return self.vm.Run(code)
obscuren's avatar
obscuren committed
82 83
}

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
func (self *JSRE) Require(file string) error {
	if len(filepath.Ext(file)) == 0 {
		file += ".js"
	}

	fh, err := os.Open(file)
	if err != nil {
		return err
	}

	content, _ := ioutil.ReadAll(fh)
	self.Run("exports = {};(function() {" + string(content) + "})();")

	return nil
}

obscuren's avatar
obscuren committed
100 101 102 103 104 105 106
func (self *JSRE) Stop() {
	// Kill the main loop
	self.quitChan <- true

	close(self.blockChan)
	close(self.quitChan)
	close(self.changeChan)
zelig's avatar
zelig committed
107
	jsrelogger.Infoln("stopped")
obscuren's avatar
obscuren committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
}

func (self *JSRE) mainLoop() {
	// Subscribe to events
	reactor := self.ethereum.Reactor()
	reactor.Subscribe("newBlock", self.blockChan)

out:
	for {
		select {
		case <-self.quitChan:
			break out
		case block := <-self.blockChan:
			if _, ok := block.Resource.(*ethchain.Block); ok {
			}
		case object := <-self.changeChan:
			if stateObject, ok := object.Resource.(*ethchain.StateObject); ok {
125
				for _, cb := range self.objectCb[ethutil.Bytes2Hex(stateObject.Address())] {
obscuren's avatar
obscuren committed
126 127 128 129
					val, _ := self.vm.ToValue(ethpub.NewPStateObject(stateObject))
					cb.Call(cb, val)
				}
			} else if storageObject, ok := object.Resource.(*ethchain.StorageState); ok {
130
				for _, cb := range self.objectCb[ethutil.Bytes2Hex(storageObject.StateAddress)+ethutil.Bytes2Hex(storageObject.Address)] {
131 132 133
					val, _ := self.vm.ToValue(ethpub.NewPStorageState(storageObject))
					cb.Call(cb, val)
				}
obscuren's avatar
obscuren committed
134 135 136 137 138
			}
		}
	}
}

obscuren's avatar
obscuren committed
139 140 141
func (self *JSRE) initStdFuncs() {
	t, _ := self.vm.Get("eth")
	eth := t.Object()
142 143
	eth.Set("watch", self.watch)
	eth.Set("addPeer", self.addPeer)
obscuren's avatar
obscuren committed
144
	eth.Set("require", self.require)
obscuren's avatar
obscuren committed
145 146
	eth.Set("stopMining", self.stopMining)
	eth.Set("startMining", self.startMining)
obscuren's avatar
obscuren committed
147
	eth.Set("execBlock", self.execBlock)
148 149 150 151 152 153
}

/*
 * The following methods are natively implemented javascript functions
 */

obscuren's avatar
obscuren committed
154 155 156 157 158 159 160 161 162 163
func (self *JSRE) stopMining(call otto.FunctionCall) otto.Value {
	v, _ := self.vm.ToValue(utils.StopMining(self.ethereum))
	return v
}

func (self *JSRE) startMining(call otto.FunctionCall) otto.Value {
	v, _ := self.vm.ToValue(utils.StartMining(self.ethereum))
	return v
}

164
// eth.watch
obscuren's avatar
obscuren committed
165
func (self *JSRE) watch(call otto.FunctionCall) otto.Value {
166 167 168 169 170 171 172 173 174 175 176 177 178 179
	addr, _ := call.Argument(0).ToString()
	var storageAddr string
	var cb otto.Value
	var storageCallback bool
	if len(call.ArgumentList) > 2 {
		storageCallback = true
		storageAddr, _ = call.Argument(1).ToString()
		cb = call.Argument(2)
	} else {
		cb = call.Argument(1)
	}

	if storageCallback {
		self.objectCb[addr+storageAddr] = append(self.objectCb[addr+storageAddr], cb)
obscuren's avatar
obscuren committed
180

181
		event := "storage:" + string(ethutil.Hex2Bytes(addr)) + ":" + string(ethutil.Hex2Bytes(storageAddr))
182 183
		self.ethereum.Reactor().Subscribe(event, self.changeChan)
	} else {
obscuren's avatar
obscuren committed
184
		self.objectCb[addr] = append(self.objectCb[addr], cb)
obscuren's avatar
obscuren committed
185

186
		event := "object:" + string(ethutil.Hex2Bytes(addr))
obscuren's avatar
obscuren committed
187
		self.ethereum.Reactor().Subscribe(event, self.changeChan)
188 189 190 191
	}

	return otto.UndefinedValue()
}
192

193 194 195 196 197 198 199 200 201 202 203 204 205
func (self *JSRE) addPeer(call otto.FunctionCall) otto.Value {
	host, err := call.Argument(0).ToString()
	if err != nil {
		return otto.FalseValue()
	}
	self.ethereum.ConnectToPeer(host)

	return otto.TrueValue()
}

func (self *JSRE) require(call otto.FunctionCall) otto.Value {
	file, err := call.Argument(0).ToString()
	if err != nil {
206
		return otto.UndefinedValue()
207 208 209 210 211 212 213
	}
	if err := self.Require(file); err != nil {
		fmt.Println("err:", err)
		return otto.UndefinedValue()
	}

	t, _ := self.vm.Get("exports")
214

215
	return t
obscuren's avatar
obscuren committed
216
}
217 218 219 220 221 222 223

func (self *JSRE) execBlock(call otto.FunctionCall) otto.Value {
	hash, err := call.Argument(0).ToString()
	if err != nil {
		return otto.UndefinedValue()
	}

224
	err = utils.BlockDo(self.ethereum, ethutil.Hex2Bytes(hash))
225 226 227 228 229 230 231
	if err != nil {
		fmt.Println(err)
		return otto.FalseValue()
	}

	return otto.TrueValue()
}