package main import ( "encoding/json" "errors" "fmt" "math/big" "strconv" "time" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/natspec" "github.com/ethereum/go-ethereum/common/resolver" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/xeth" "github.com/robertkrimen/otto" "gopkg.in/fatih/set.v0" ) /* node admin bindings */ func (js *jsre) adminBindings() { ethO, _ := js.re.Get("eth") eth := ethO.Object() eth.Set("pendingTransactions", js.pendingTransactions) eth.Set("resend", js.resend) eth.Set("sign", js.sign) js.re.Set("admin", struct{}{}) t, _ := js.re.Get("admin") admin := t.Object() admin.Set("addPeer", js.addPeer) admin.Set("startRPC", js.startRPC) admin.Set("stopRPC", js.stopRPC) admin.Set("nodeInfo", js.nodeInfo) admin.Set("peers", js.peers) admin.Set("newAccount", js.newAccount) admin.Set("unlock", js.unlock) admin.Set("import", js.importChain) admin.Set("export", js.exportChain) admin.Set("verbosity", js.verbosity) admin.Set("progress", js.syncProgress) admin.Set("setSolc", js.setSolc) admin.Set("contractInfo", struct{}{}) t, _ = admin.Get("contractInfo") cinfo := t.Object() // newRegistry officially not documented temporary option cinfo.Set("start", js.startNatSpec) cinfo.Set("stop", js.stopNatSpec) cinfo.Set("newRegistry", js.newRegistry) cinfo.Set("get", js.getContractInfo) cinfo.Set("register", js.register) cinfo.Set("registerUrl", js.registerUrl) // cinfo.Set("verify", js.verify) admin.Set("miner", struct{}{}) t, _ = admin.Get("miner") miner := t.Object() miner.Set("start", js.startMining) miner.Set("stop", js.stopMining) miner.Set("hashrate", js.hashrate) miner.Set("setExtra", js.setExtra) miner.Set("setGasPrice", js.setGasPrice) miner.Set("startAutoDAG", js.startAutoDAG) miner.Set("stopAutoDAG", js.stopAutoDAG) miner.Set("makeDAG", js.makeDAG) admin.Set("txPool", struct{}{}) t, _ = admin.Get("txPool") txPool := t.Object() txPool.Set("pending", js.allPendingTransactions) txPool.Set("queued", js.allQueuedTransactions) admin.Set("debug", struct{}{}) t, _ = admin.Get("debug") debug := t.Object() js.re.Set("sleep", js.sleep) debug.Set("backtrace", js.backtrace) debug.Set("printBlock", js.printBlock) debug.Set("dumpBlock", js.dumpBlock) debug.Set("getBlockRlp", js.getBlockRlp) debug.Set("setHead", js.setHead) debug.Set("processBlock", js.debugBlock) debug.Set("seedhash", js.seedHash) debug.Set("insertBlock", js.insertBlockRlp) // undocumented temporary debug.Set("waitForBlocks", js.waitForBlocks) } // generic helper to getBlock by Number/Height or Hex depending on autodetected input // if argument is missing the current block is returned // if block is not found or there is problem with decoding // the appropriate value is returned and block is guaranteed to be nil func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) { var block *types.Block if len(call.ArgumentList) > 0 { if call.Argument(0).IsNumber() { num, _ := call.Argument(0).ToInteger() block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num)) } else if call.Argument(0).IsString() { hash, _ := call.Argument(0).ToString() block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash)) } else { return nil, errors.New("invalid argument for dump. Either hex string or number") } } else { block = js.ethereum.ChainManager().CurrentBlock() } if block == nil { return nil, errors.New("block not found") } return block, nil } func (js *jsre) seedHash(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) > 0 { if call.Argument(0).IsNumber() { num, _ := call.Argument(0).ToInteger() hash, err := ethash.GetSeedHash(uint64(num)) if err != nil { fmt.Println(err) return otto.UndefinedValue() } v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash)) return v } else { fmt.Println("arg not a number") } } else { fmt.Println("requires number argument") } return otto.UndefinedValue() } func (js *jsre) allPendingTransactions(call otto.FunctionCall) otto.Value { txs := js.ethereum.TxPool().GetTransactions() ltxs := make([]*tx, len(txs)) for i, tx := range txs { // no need to check err ltxs[i] = newTx(tx) } v, _ := call.Otto.ToValue(ltxs) return v } func (js *jsre) allQueuedTransactions(call otto.FunctionCall) otto.Value { txs := js.ethereum.TxPool().GetQueuedTransactions() ltxs := make([]*tx, len(txs)) for i, tx := range txs { // no need to check err ltxs[i] = newTx(tx) } v, _ := call.Otto.ToValue(ltxs) return v } func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value { txs := js.ethereum.TxPool().GetTransactions() // grab the accounts from the account manager. This will help with determening which // transactions should be returned. accounts, err := js.ethereum.AccountManager().Accounts() if err != nil { fmt.Println(err) return otto.UndefinedValue() } // Add the accouns to a new set accountSet := set.New() for _, account := range accounts { accountSet.Add(account.Address) } //ltxs := make([]*tx, len(txs)) var ltxs []*tx for _, tx := range txs { if from, _ := tx.From(); accountSet.Has(from) { ltxs = append(ltxs, newTx(tx)) } } v, _ := call.Otto.ToValue(ltxs) return v } func (js *jsre) resend(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) == 0 { fmt.Println("first argument must be a transaction") return otto.FalseValue() } v, err := call.Argument(0).Export() if err != nil { fmt.Println(err) return otto.FalseValue() } if tx, ok := v.(*tx); ok { gl, gp := tx.GasLimit, tx.GasPrice if len(call.ArgumentList) > 1 { gp = call.Argument(1).String() } if len(call.ArgumentList) > 2 { gl = call.Argument(2).String() } ret, err := js.xeth.Transact(tx.From, tx.To, tx.Nonce, tx.Value, gl, gp, tx.Data) if err != nil { fmt.Println(err) return otto.FalseValue() } js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx}) v, _ := call.Otto.ToValue(ret) return v } fmt.Println("first argument must be a transaction") return otto.FalseValue() } func (js *jsre) sign(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 2 { fmt.Println("requires 2 arguments: eth.sign(signer, data)") return otto.UndefinedValue() } signer, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } data, err := call.Argument(1).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } signed, err := js.xeth.Sign(signer, data, false) if err != nil { fmt.Println(err) return otto.UndefinedValue() } v, _ := call.Otto.ToValue(signed) return v } func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { block, err := js.getBlock(call) if err != nil { fmt.Println(err) return otto.UndefinedValue() } tstart := time.Now() old := vm.Debug vm.Debug = true _, err = js.ethereum.BlockProcessor().RetryProcess(block) if err != nil { fmt.Println(err) r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) return r } vm.Debug = old r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) return r } func (js *jsre) insertBlockRlp(call otto.FunctionCall) otto.Value { tstart := time.Now() var block types.Block if call.Argument(0).IsString() { blockRlp, _ := call.Argument(0).ToString() err := rlp.DecodeBytes(common.Hex2Bytes(blockRlp), &block) if err != nil { fmt.Println(err) return otto.UndefinedValue() } } old := vm.Debug vm.Debug = true _, err := js.ethereum.BlockProcessor().RetryProcess(&block) if err != nil { fmt.Println(err) r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()}) return r } vm.Debug = old r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()}) return r } func (js *jsre) setHead(call otto.FunctionCall) otto.Value { block, err := js.getBlock(call) if err != nil { fmt.Println(err) return otto.UndefinedValue() } js.ethereum.ChainManager().SetHead(block) return otto.UndefinedValue() } func (js *jsre) syncProgress(call otto.FunctionCall) otto.Value { pending, cached, importing, eta := js.ethereum.Downloader().Stats() v, _ := call.Otto.ToValue(map[string]interface{}{ "pending": pending, "cached": cached, "importing": importing, "estimate": (eta / time.Second * time.Second).String(), }) return v } func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { block, err := js.getBlock(call) if err != nil { fmt.Println(err) return otto.UndefinedValue() } encoded, _ := rlp.EncodeToBytes(block) v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded)) return v } func (js *jsre) setExtra(call otto.FunctionCall) otto.Value { extra, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } if len(extra) > 1024 { fmt.Println("error: cannot exceed 1024 bytes") return otto.UndefinedValue() } js.ethereum.Miner().SetExtra([]byte(extra)) return otto.UndefinedValue() } func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value { gasPrice, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } js.ethereum.Miner().SetGasPrice(common.String2Big(gasPrice)) return otto.UndefinedValue() } func (js *jsre) hashrate(call otto.FunctionCall) otto.Value { v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate()) return v } func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value { blockNumber, err := call.Argument(1).ToInteger() if err != nil { fmt.Println(err) return otto.FalseValue() } err = ethash.MakeDAG(uint64(blockNumber), "") if err != nil { return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value { js.ethereum.StartAutoDAG() return otto.TrueValue() } func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value { js.ethereum.StopAutoDAG() return otto.TrueValue() } func (js *jsre) backtrace(call otto.FunctionCall) otto.Value { tracestr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } glog.GetTraceLocation().Set(tracestr) return otto.UndefinedValue() } func (js *jsre) verbosity(call otto.FunctionCall) otto.Value { v, err := call.Argument(0).ToInteger() if err != nil { fmt.Println(err) return otto.UndefinedValue() } glog.SetV(int(v)) return otto.UndefinedValue() } func (js *jsre) startMining(call otto.FunctionCall) otto.Value { var ( threads int64 err error ) if len(call.ArgumentList) > 0 { threads, err = call.Argument(0).ToInteger() if err != nil { fmt.Println(err) return otto.FalseValue() } } else { threads = int64(js.ethereum.MinerThreads) } // switch on DAG autogeneration when miner starts js.ethereum.StartAutoDAG() err = js.ethereum.StartMining(int(threads)) if err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) stopMining(call otto.FunctionCall) otto.Value { js.ethereum.StopMining() js.ethereum.StopAutoDAG() return otto.TrueValue() } func (js *jsre) startRPC(call otto.FunctionCall) otto.Value { addr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } port, err := call.Argument(1).ToInteger() if err != nil { fmt.Println(err) return otto.FalseValue() } corsDomain := js.corsDomain if len(call.ArgumentList) > 2 { corsDomain, err = call.Argument(2).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } } config := rpc.RpcConfig{ ListenAddress: addr, ListenPort: uint(port), CorsDomain: corsDomain, } xeth := xeth.New(js.ethereum, nil) err = rpc.Start(xeth, config) if err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value { if rpc.Stop() == nil { return otto.TrueValue() } return otto.FalseValue() } func (js *jsre) addPeer(call otto.FunctionCall) otto.Value { nodeURL, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } err = js.ethereum.AddPeer(nodeURL) if err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) unlock(call otto.FunctionCall) otto.Value { addr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } seconds, err := call.Argument(2).ToInteger() if err != nil { fmt.Println(err) return otto.FalseValue() } if seconds == 0 { seconds = accounts.DefaultAccountUnlockDuration } arg := call.Argument(1) var passphrase string if arg.IsUndefined() { fmt.Println("Please enter a passphrase now.") passphrase, err = utils.PromptPassword("Passphrase: ", true) if err != nil { fmt.Println(err) return otto.FalseValue() } } else { passphrase, err = arg.ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } } am := js.ethereum.AccountManager() err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second) if err != nil { fmt.Printf("Unlock account failed '%v'\n", err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) newAccount(call otto.FunctionCall) otto.Value { arg := call.Argument(0) var passphrase string if arg.IsUndefined() { fmt.Println("The new account will be encrypted with a passphrase.") fmt.Println("Please enter a passphrase now.") auth, err := utils.PromptPassword("Passphrase: ", true) if err != nil { fmt.Println(err) return otto.FalseValue() } confirm, err := utils.PromptPassword("Repeat Passphrase: ", false) if err != nil { fmt.Println(err) return otto.FalseValue() } if auth != confirm { fmt.Println("Passphrases did not match.") return otto.FalseValue() } passphrase = auth } else { var err error passphrase, err = arg.ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } } acct, err := js.ethereum.AccountManager().NewAccount(passphrase) if err != nil { fmt.Printf("Could not create the account: %v", err) return otto.UndefinedValue() } v, _ := call.Otto.ToValue(acct.Address.Hex()) return v } func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value { v, _ := call.Otto.ToValue(js.ethereum.NodeInfo()) return v } func (js *jsre) peers(call otto.FunctionCall) otto.Value { v, _ := call.Otto.ToValue(js.ethereum.PeersInfo()) return v } func (js *jsre) importChain(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) == 0 { fmt.Println("require file name. admin.importChain(filename)") return otto.FalseValue() } fn, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } if err := utils.ImportChain(js.ethereum.ChainManager(), fn); err != nil { fmt.Println("Import error: ", err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) exportChain(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) == 0 { fmt.Println("require file name: admin.exportChain(filename)") return otto.FalseValue() } fn, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } if err := utils.ExportChain(js.ethereum.ChainManager(), fn); err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) printBlock(call otto.FunctionCall) otto.Value { block, err := js.getBlock(call) if err != nil { fmt.Println(err) return otto.UndefinedValue() } fmt.Println(block) return otto.UndefinedValue() } func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value { block, err := js.getBlock(call) if err != nil { fmt.Println(err) return otto.UndefinedValue() } statedb := state.New(block.Root(), js.ethereum.StateDb()) dump := statedb.RawDump() v, _ := call.Otto.ToValue(dump) return v } func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) > 2 { fmt.Println("requires 0, 1 or 2 arguments: admin.debug.waitForBlock(minHeight, timeout)") return otto.FalseValue() } var n, timeout int64 var timer <-chan time.Time var height *big.Int var err error args := len(call.ArgumentList) if args == 2 { timeout, err = call.Argument(1).ToInteger() if err != nil { fmt.Println(err) return otto.UndefinedValue() } timer = time.NewTimer(time.Duration(timeout) * time.Second).C } if args >= 1 { n, err = call.Argument(0).ToInteger() if err != nil { fmt.Println(err) return otto.UndefinedValue() } height = big.NewInt(n) } if args == 0 { height = js.xeth.CurrentBlock().Number() height.Add(height, common.Big1) } wait := js.wait js.wait <- height select { case <-timer: // if times out make sure the xeth loop does not block go func() { select { case wait <- nil: case <-wait: } }() return otto.UndefinedValue() case height = <-wait: } v, _ := call.Otto.ToValue(height.Uint64()) return v } func (js *jsre) sleep(call otto.FunctionCall) otto.Value { sec, err := call.Argument(0).ToInteger() if err != nil { fmt.Println(err) return otto.FalseValue() } time.Sleep(time.Duration(sec) * time.Second) return otto.UndefinedValue() } func (js *jsre) setSolc(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 1 { fmt.Println("needs 1 argument: admin.contractInfo.setSolc(solcPath)") return otto.FalseValue() } solcPath, err := call.Argument(0).ToString() if err != nil { return otto.FalseValue() } solc, err := js.xeth.SetSolc(solcPath) if err != nil { fmt.Println(err) return otto.FalseValue() } fmt.Println(solc.Info()) return otto.TrueValue() } func (js *jsre) register(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 4 { fmt.Println("requires 4 arguments: admin.contractInfo.register(fromaddress, contractaddress, contract, filename)") return otto.UndefinedValue() } sender, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } address, err := call.Argument(1).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } raw, err := call.Argument(2).Export() if err != nil { fmt.Println(err) return otto.UndefinedValue() } jsonraw, err := json.Marshal(raw) if err != nil { fmt.Println(err) return otto.UndefinedValue() } var contract compiler.Contract err = json.Unmarshal(jsonraw, &contract) if err != nil { fmt.Println(err) return otto.UndefinedValue() } filename, err := call.Argument(3).ToString() if err != nil { fmt.Println(err) return otto.UndefinedValue() } contenthash, err := compiler.ExtractInfo(&contract, filename) if err != nil { fmt.Println(err) return otto.UndefinedValue() } // sender and contract address are passed as hex strings codeb := js.xeth.CodeAtBytes(address) codehash := common.BytesToHash(crypto.Sha3(codeb)) if err != nil { fmt.Println(err) return otto.UndefinedValue() } registry := resolver.New(js.xeth) _, err = registry.RegisterContentHash(common.HexToAddress(sender), codehash, contenthash) if err != nil { fmt.Println(err) return otto.UndefinedValue() } v, _ := call.Otto.ToValue(contenthash.Hex()) return v } func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 3 { fmt.Println("requires 3 arguments: admin.contractInfo.register(fromaddress, contenthash, filename)") return otto.FalseValue() } sender, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } contenthash, err := call.Argument(1).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } url, err := call.Argument(2).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } registry := resolver.New(js.xeth) _, err = registry.RegisterUrl(common.HexToAddress(sender), common.HexToHash(contenthash), url) if err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 1 { fmt.Println("requires 1 argument: admin.contractInfo.register(contractaddress)") return otto.FalseValue() } addr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } infoDoc, err := natspec.FetchDocsForContract(addr, js.xeth, ds) if err != nil { fmt.Println(err) return otto.UndefinedValue() } var info compiler.ContractInfo err = json.Unmarshal(infoDoc, &info) if err != nil { fmt.Println(err) return otto.UndefinedValue() } v, _ := call.Otto.ToValue(info) return v } func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value { js.ethereum.NatSpec = true return otto.TrueValue() } func (js *jsre) stopNatSpec(call otto.FunctionCall) otto.Value { js.ethereum.NatSpec = false return otto.TrueValue() } func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value { if len(call.ArgumentList) != 1 { fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)") return otto.FalseValue() } addr, err := call.Argument(0).ToString() if err != nil { fmt.Println(err) return otto.FalseValue() } registry := resolver.New(js.xeth) err = registry.CreateContracts(common.HexToAddress(addr)) if err != nil { fmt.Println(err) return otto.FalseValue() } return otto.TrueValue() } // internal transaction type which will allow us to resend transactions using `eth.resend` type tx struct { tx *types.Transaction To string From string Nonce string Value string Data string GasLimit string GasPrice string } func newTx(t *types.Transaction) *tx { from, _ := t.From() var to string if t := t.To(); t != nil { to = t.Hex() } return &tx{ tx: t, To: to, From: from.Hex(), Value: t.Amount.String(), Nonce: strconv.Itoa(int(t.Nonce())), Data: "0x" + common.Bytes2Hex(t.Data()), GasLimit: t.GasLimit.String(), GasPrice: t.GasPrice().String(), } }