diff options
Diffstat (limited to 'javascript')
-rw-r--r-- | javascript/javascript_runtime.go | 215 | ||||
-rw-r--r-- | javascript/js_lib.go | 55 | ||||
-rw-r--r-- | javascript/types.go | 112 |
3 files changed, 382 insertions, 0 deletions
diff --git a/javascript/javascript_runtime.go b/javascript/javascript_runtime.go new file mode 100644 index 000000000..beaca45b9 --- /dev/null +++ b/javascript/javascript_runtime.go @@ -0,0 +1,215 @@ +package javascript + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/state" + "github.com/ethereum/go-ethereum/xeth" + "github.com/obscuren/otto" +) + +var jsrelogger = logger.NewLogger("JSRE") + +type JSRE struct { + ethereum *eth.Ethereum + Vm *otto.Otto + xeth *xeth.XEth + + events event.Subscription + + objectCb map[string][]otto.Value +} + +func (jsre *JSRE) LoadExtFile(path string) { + result, err := ioutil.ReadFile(path) + if err == nil { + jsre.Vm.Run(result) + } else { + jsrelogger.Infoln("Could not load file:", path) + } +} + +func (jsre *JSRE) LoadIntFile(file string) { + assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") + jsre.LoadExtFile(path.Join(assetPath, file)) +} + +func NewJSRE(ethereum *eth.Ethereum) *JSRE { + re := &JSRE{ + ethereum, + otto.New(), + xeth.New(ethereum), + nil, + make(map[string][]otto.Value), + } + + // Init the JS lib + re.Vm.Run(jsLib) + + // Load extra javascript files + re.LoadIntFile("bignumber.min.js") + + // Subscribe to events + mux := ethereum.EventMux() + re.events = mux.Subscribe(core.NewBlockEvent{}) + + // We have to make sure that, whoever calls this, calls "Stop" + go re.mainLoop() + + re.Bind("eth", &JSEthereum{re.xeth, re.Vm, ethereum}) + + re.initStdFuncs() + + jsrelogger.Infoln("started") + + return re +} + +func (self *JSRE) Bind(name string, v interface{}) { + self.Vm.Set(name, v) +} + +func (self *JSRE) Run(code string) (otto.Value, error) { + return self.Vm.Run(code) +} + +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 +} + +func (self *JSRE) Stop() { + self.events.Unsubscribe() + jsrelogger.Infoln("stopped") +} + +func (self *JSRE) mainLoop() { + for _ = range self.events.Chan() { + } +} + +func (self *JSRE) initStdFuncs() { + t, _ := self.Vm.Get("eth") + eth := t.Object() + eth.Set("connect", self.connect) + eth.Set("require", self.require) + eth.Set("stopMining", self.stopMining) + eth.Set("startMining", self.startMining) + eth.Set("dump", self.dump) + eth.Set("export", self.export) +} + +/* + * The following methods are natively implemented javascript functions + */ + +func (self *JSRE) dump(call otto.FunctionCall) otto.Value { + var block *types.Block + + if len(call.ArgumentList) > 0 { + if call.Argument(0).IsNumber() { + num, _ := call.Argument(0).ToInteger() + block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num)) + } else if call.Argument(0).IsString() { + hash, _ := call.Argument(0).ToString() + block = self.ethereum.ChainManager().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() + } + + } else { + block = self.ethereum.ChainManager().CurrentBlock() + } + + statedb := state.New(block.Root(), self.ethereum.Db()) + + v, _ := self.Vm.ToValue(statedb.RawDump()) + + return v +} + +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 +} + +func (self *JSRE) connect(call otto.FunctionCall) otto.Value { + nodeURL, err := call.Argument(0).ToString() + if err != nil { + return otto.FalseValue() + } + if err := self.ethereum.SuggestPeer(nodeURL); err != nil { + return otto.FalseValue() + } + return otto.TrueValue() +} + +func (self *JSRE) require(call otto.FunctionCall) otto.Value { + file, err := call.Argument(0).ToString() + if err != nil { + return otto.UndefinedValue() + } + if err := self.Require(file); err != nil { + fmt.Println("err:", err) + return otto.UndefinedValue() + } + + t, _ := self.Vm.Get("exports") + + return t +} + +func (self *JSRE) export(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) == 0 { + fmt.Println("err: require file name") + return otto.FalseValue() + } + + fn, err := call.Argument(0).ToString() + if err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + data := self.ethereum.ChainManager().Export() + + if err := ethutil.WriteFile(fn, data); err != nil { + fmt.Println(err) + return otto.FalseValue() + } + + return otto.TrueValue() +} diff --git a/javascript/js_lib.go b/javascript/js_lib.go new file mode 100644 index 000000000..f828ca389 --- /dev/null +++ b/javascript/js_lib.go @@ -0,0 +1,55 @@ +package javascript + +const jsLib = ` +function pp(object) { + var str = ""; + + if(object instanceof Array) { + str += "[ "; + for(var i = 0, l = object.length; i < l; i++) { + str += pp(object[i]); + + if(i < l-1) { + str += ", "; + } + } + str += " ]"; + } else if(typeof(object) === "object") { + str += "{ "; + var last = Object.keys(object).pop() + for(var k in object) { + str += k + ": " + pp(object[k]); + + if(k !== last) { + str += ", "; + } + } + str += " }"; + } else if(typeof(object) === "string") { + str += "\033[32m'" + object + "'"; + } else if(typeof(object) === "undefined") { + str += "\033[1m\033[30m" + object; + } else if(typeof(object) === "number") { + str += "\033[31m" + object; + } else if(typeof(object) === "function") { + str += "\033[35m[Function]"; + } else { + str += object; + } + + str += "\033[0m"; + + return str; +} + +function prettyPrint(/* */) { + var args = arguments; + var ret = ""; + for(var i = 0, l = args.length; i < l; i++) { + ret += pp(args[i]) + "\n"; + } + return ret; +} + +var print = prettyPrint; +` diff --git a/javascript/types.go b/javascript/types.go new file mode 100644 index 000000000..17f1b739e --- /dev/null +++ b/javascript/types.go @@ -0,0 +1,112 @@ +package javascript + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/state" + "github.com/ethereum/go-ethereum/ui" + "github.com/ethereum/go-ethereum/xeth" + "github.com/obscuren/otto" +) + +type JSStateObject struct { + *xeth.Object + eth *JSEthereum +} + +func (self *JSStateObject) EachStorage(call otto.FunctionCall) otto.Value { + cb := call.Argument(0) + + it := self.Object.Trie().Iterator() + for it.Next() { + cb.Call(self.eth.toVal(self), self.eth.toVal(ethutil.Bytes2Hex(it.Key)), self.eth.toVal(ethutil.Bytes2Hex(it.Value))) + } + + return otto.UndefinedValue() +} + +// The JSEthereum object attempts to wrap the PEthereum object and returns +// meaningful javascript objects +type JSBlock struct { + *xeth.Block + eth *JSEthereum +} + +func (self *JSBlock) GetTransaction(hash string) otto.Value { + return self.eth.toVal(self.Block.GetTransaction(hash)) +} + +type JSLog struct { + Address string `json:address` + Topics []string `json:topics` + Number int32 `json:number` + Data string `json:data` +} + +func NewJSLog(log state.Log) JSLog { + return JSLog{ + Address: ethutil.Bytes2Hex(log.Address()), + Topics: nil, //ethutil.Bytes2Hex(log.Address()), + Number: 0, + Data: ethutil.Bytes2Hex(log.Data()), + } +} + +type JSEthereum struct { + *xeth.XEth + vm *otto.Otto + ethereum *eth.Ethereum +} + +func (self *JSEthereum) Block(v interface{}) otto.Value { + if number, ok := v.(int64); ok { + return self.toVal(&JSBlock{self.XEth.BlockByNumber(int32(number)), self}) + } else if hash, ok := v.(string); ok { + return self.toVal(&JSBlock{self.XEth.BlockByHash(hash), self}) + } + + return otto.UndefinedValue() +} + +func (self *JSEthereum) GetStateObject(addr string) otto.Value { + return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self}) +} + +func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { + r, err := self.XEth.Transact(recipient, valueStr, gasStr, gasPriceStr, dataStr) + if err != nil { + fmt.Println(err) + + return otto.UndefinedValue() + } + + return self.toVal(r) +} + +func (self *JSEthereum) toVal(v interface{}) otto.Value { + result, err := self.vm.ToValue(v) + + if err != nil { + fmt.Println("Value unknown:", err) + + return otto.UndefinedValue() + } + + return result +} + +func (self *JSEthereum) Messages(object map[string]interface{}) otto.Value { + filter := ui.NewFilterFromMap(object, self.ethereum) + + logs := filter.Find() + var jslogs []JSLog + for _, m := range logs { + jslogs = append(jslogs, NewJSLog(m)) + } + + v, _ := self.vm.ToValue(jslogs) + + return v +} |