javascript_runtime.go 5.56 KB
Newer Older
1
package javascript
obscuren's avatar
obscuren committed
2 3 4

import (
	"fmt"
obscuren's avatar
obscuren committed
5 6 7 8 9
	"io/ioutil"
	"os"
	"path"
	"path/filepath"

obscuren's avatar
obscuren committed
10
	"github.com/ethereum/eth-go"
obscuren's avatar
obscuren committed
11
	"github.com/ethereum/eth-go/ethchain"
zelig's avatar
zelig committed
12
	"github.com/ethereum/eth-go/ethlog"
obscuren's avatar
obscuren committed
13
	"github.com/ethereum/eth-go/ethpipe"
zelig's avatar
zelig committed
14
	"github.com/ethereum/eth-go/ethreact"
obscuren's avatar
obscuren committed
15
	"github.com/ethereum/eth-go/ethstate"
obscuren's avatar
obscuren committed
16
	"github.com/ethereum/eth-go/ethutil"
obscuren's avatar
obscuren committed
17
	"github.com/ethereum/go-ethereum/utils"
18
	"github.com/obscuren/otto"
obscuren's avatar
obscuren committed
19 20
)

zelig's avatar
zelig committed
21 22
var jsrelogger = ethlog.NewLogger("JSRE")

obscuren's avatar
obscuren committed
23
type JSRE struct {
obscuren's avatar
obscuren committed
24
	ethereum *eth.Ethereum
25
	Vm       *otto.Otto
obscuren's avatar
obscuren committed
26
	pipe     *ethpipe.JSPipe
obscuren's avatar
obscuren committed
27

zelig's avatar
zelig committed
28 29
	blockChan  chan ethreact.Event
	changeChan chan ethreact.Event
obscuren's avatar
obscuren committed
30 31 32
	quitChan   chan bool

	objectCb map[string][]otto.Value
obscuren's avatar
obscuren committed
33 34
}

35 36 37
func (jsre *JSRE) LoadExtFile(path string) {
	result, err := ioutil.ReadFile(path)
	if err == nil {
38
		jsre.Vm.Run(result)
39
	} else {
40
		jsrelogger.Infoln("Could not load file:", path)
41 42 43 44 45 46 47 48
	}
}

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
49
func NewJSRE(ethereum *eth.Ethereum) *JSRE {
obscuren's avatar
obscuren committed
50 51 52
	re := &JSRE{
		ethereum,
		otto.New(),
obscuren's avatar
obscuren committed
53
		ethpipe.NewJSPipe(ethereum),
zelig's avatar
zelig committed
54 55
		make(chan ethreact.Event, 10),
		make(chan ethreact.Event, 10),
obscuren's avatar
obscuren committed
56 57 58 59
		make(chan bool),
		make(map[string][]otto.Value),
	}

obscuren's avatar
obscuren committed
60
	// Init the JS lib
61
	re.Vm.Run(jsLib)
obscuren's avatar
obscuren committed
62

63 64 65 66
	// Load extra javascript files
	re.LoadIntFile("string.js")
	re.LoadIntFile("big.js")

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

zelig's avatar
zelig committed
70 71
	// Subscribe to events
	reactor := ethereum.Reactor()
zelig's avatar
zelig committed
72
	reactor.Subscribe("newBlock", re.blockChan)
zelig's avatar
zelig committed
73

obscuren's avatar
obscuren committed
74
	re.Bind("eth", &JSEthereum{re.pipe, re.Vm, ethereum})
obscuren's avatar
obscuren committed
75

obscuren's avatar
obscuren committed
76
	re.initStdFuncs()
obscuren's avatar
obscuren committed
77

zelig's avatar
zelig committed
78 79
	jsrelogger.Infoln("started")

obscuren's avatar
obscuren committed
80 81
	return re
}
obscuren's avatar
obscuren committed
82

obscuren's avatar
obscuren committed
83
func (self *JSRE) Bind(name string, v interface{}) {
84
	self.Vm.Set(name, v)
obscuren's avatar
obscuren committed
85
}
obscuren's avatar
obscuren committed
86

obscuren's avatar
obscuren committed
87
func (self *JSRE) Run(code string) (otto.Value, error) {
88
	return self.Vm.Run(code)
obscuren's avatar
obscuren committed
89 90
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
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
107 108 109 110 111 112 113
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
114
	jsrelogger.Infoln("stopped")
obscuren's avatar
obscuren committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
}

func (self *JSRE) mainLoop() {
out:
	for {
		select {
		case <-self.quitChan:
			break out
		case block := <-self.blockChan:
			if _, ok := block.Resource.(*ethchain.Block); ok {
			}
		}
	}
}

obscuren's avatar
obscuren committed
130
func (self *JSRE) initStdFuncs() {
131
	t, _ := self.Vm.Get("eth")
obscuren's avatar
obscuren committed
132
	eth := t.Object()
133 134
	eth.Set("watch", self.watch)
	eth.Set("addPeer", self.addPeer)
obscuren's avatar
obscuren committed
135
	eth.Set("require", self.require)
obscuren's avatar
obscuren committed
136 137
	eth.Set("stopMining", self.stopMining)
	eth.Set("startMining", self.startMining)
obscuren's avatar
obscuren committed
138
	eth.Set("execBlock", self.execBlock)
obscuren's avatar
obscuren committed
139
	eth.Set("dump", self.dump)
140 141 142 143 144 145
}

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

obscuren's avatar
obscuren committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
func (self *JSRE) dump(call otto.FunctionCall) otto.Value {
	var state *ethstate.State

	if len(call.ArgumentList) > 0 {
		var block *ethchain.Block
		if call.Argument(0).IsNumber() {
			num, _ := call.Argument(0).ToInteger()
			block = self.ethereum.BlockChain().GetBlockByNumber(uint64(num))
		} else if call.Argument(0).IsString() {
			hash, _ := call.Argument(0).ToString()
			block = self.ethereum.BlockChain().GetBlock(ethutil.Hex2Bytes(hash))
		} else {
			fmt.Println("invalid argument for dump. Either hex string or number")
		}

		if block == nil {
			fmt.Println("block not found")

			return otto.UndefinedValue()
		}

		state = block.State()
	} else {
		state = self.ethereum.StateManager().CurrentState()
	}

172
	v, _ := self.Vm.ToValue(state.Dump())
obscuren's avatar
obscuren committed
173

174
	return v
obscuren's avatar
obscuren committed
175 176
}

obscuren's avatar
obscuren committed
177
func (self *JSRE) stopMining(call otto.FunctionCall) otto.Value {
178
	v, _ := self.Vm.ToValue(utils.StopMining(self.ethereum))
obscuren's avatar
obscuren committed
179 180 181 182
	return v
}

func (self *JSRE) startMining(call otto.FunctionCall) otto.Value {
183
	v, _ := self.Vm.ToValue(utils.StartMining(self.ethereum))
obscuren's avatar
obscuren committed
184 185 186
	return v
}

187
// eth.watch
obscuren's avatar
obscuren committed
188
func (self *JSRE) watch(call otto.FunctionCall) otto.Value {
189 190 191 192 193 194 195 196 197 198 199 200 201 202
	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
203

204
		event := "storage:" + string(ethutil.Hex2Bytes(addr)) + ":" + string(ethutil.Hex2Bytes(storageAddr))
205 206
		self.ethereum.Reactor().Subscribe(event, self.changeChan)
	} else {
obscuren's avatar
obscuren committed
207
		self.objectCb[addr] = append(self.objectCb[addr], cb)
obscuren's avatar
obscuren committed
208

209
		event := "object:" + string(ethutil.Hex2Bytes(addr))
obscuren's avatar
obscuren committed
210
		self.ethereum.Reactor().Subscribe(event, self.changeChan)
211 212 213 214
	}

	return otto.UndefinedValue()
}
215

216 217 218 219 220 221 222 223 224 225 226 227 228
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 {
229
		return otto.UndefinedValue()
230 231 232 233 234 235
	}
	if err := self.Require(file); err != nil {
		fmt.Println("err:", err)
		return otto.UndefinedValue()
	}

236
	t, _ := self.Vm.Get("exports")
237

238
	return t
obscuren's avatar
obscuren committed
239
}
240 241 242 243 244 245 246

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

247
	err = utils.BlockDo(self.ethereum, ethutil.Hex2Bytes(hash))
248 249 250 251 252 253 254
	if err != nil {
		fmt.Println(err)
		return otto.FalseValue()
	}

	return otto.TrueValue()
}