cmd/ethereum: unlock accounts on JS REPL

This commit is contained in:
Felix Lange 2015-03-10 02:00:57 +01:00
parent 395da0e7c1
commit 9f0e3bd286
2 changed files with 112 additions and 105 deletions

View File

@ -22,11 +22,9 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal"
"path" "path"
"strings" "strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
@ -37,51 +35,117 @@ import (
"github.com/peterh/liner" "github.com/peterh/liner"
) )
func execJsFile(ethereum *eth.Ethereum, filename string) { type prompter interface {
file, err := os.Open(filename) AppendHistory(string)
if err != nil { Prompt(p string) (string, error)
utils.Fatalf("%v", err) PasswordPrompt(p string) (string, error)
}
content, err := ioutil.ReadAll(file)
if err != nil {
utils.Fatalf("%v", err)
}
re := javascript.NewJSRE(xeth.New(ethereum, nil))
if _, err := re.Run(string(content)); err != nil {
utils.Fatalf("Javascript Error: %v", err)
}
} }
type repl struct { type dumbPrompter struct{ r *bufio.Reader }
func (r dumbPrompter) Prompt(p string) (string, error) {
fmt.Print(p)
return r.r.ReadString('\n')
}
func (r dumbPrompter) PasswordPrompt(p string) (string, error) {
fmt.Println("!! Unsupported terminal, password will echo.")
fmt.Print(p)
input, err := bufio.NewReader(os.Stdin).ReadString('\n')
fmt.Println()
return input, err
}
func (r dumbPrompter) AppendHistory(string) {}
type jsre struct {
re *javascript.JSRE re *javascript.JSRE
ethereum *eth.Ethereum ethereum *eth.Ethereum
xeth *xeth.XEth xeth *xeth.XEth
prompt string ps1 string
lr *liner.State prompter
} }
func runREPL(ethereum *eth.Ethereum) { func newJSRE(ethereum *eth.Ethereum) *jsre {
xeth := xeth.New(ethereum, nil) js := &jsre{ethereum: ethereum, ps1: "> "}
repl := &repl{ js.xeth = xeth.New(ethereum, js)
re: javascript.NewJSRE(xeth), js.re = javascript.NewJSRE(js.xeth)
xeth: xeth, js.initStdFuncs()
ethereum: ethereum,
prompt: "> ",
}
repl.initStdFuncs()
if !liner.TerminalSupported() { if !liner.TerminalSupported() {
repl.dumbRead() js.prompter = dumbPrompter{bufio.NewReader(os.Stdin)}
} else { } else {
lr := liner.NewLiner() lr := liner.NewLiner()
defer lr.Close()
lr.SetCtrlCAborts(true) lr.SetCtrlCAborts(true)
repl.withHistory(func(hist *os.File) { lr.ReadHistory(hist) }) defer lr.Close()
repl.read(lr) js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
repl.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) defer js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
js.prompter = lr
}
return js
}
func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool {
p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx)
answer, _ := self.prompter.Prompt(p)
return strings.HasPrefix(strings.Trim(answer, " "), "y")
}
func (self *jsre) UnlockAccount(addr []byte) bool {
fmt.Printf("Please unlock account %x.\n", addr)
pass, err := self.prompter.PasswordPrompt("Passphrase: ")
if err != nil {
return false
}
// TODO: allow retry
if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil {
fmt.Println("Unlocking failed: ", err)
return false
} else {
fmt.Println("Account is now unlocked for this session.")
return true
} }
} }
func (self *repl) withHistory(op func(*os.File)) { func (self *jsre) exec(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
if _, err := self.re.Run(string(content)); err != nil {
return fmt.Errorf("Javascript Error: %v", err)
}
return nil
}
func (self *jsre) interactive() {
for {
input, err := self.prompter.Prompt(self.ps1)
if err != nil {
return
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
if input == "exit" {
return
}
hist := str[:len(str)-1]
self.prompter.AppendHistory(hist)
self.parseInput(str)
str = ""
}
}
}
func (self *jsre) withHistory(op func(*os.File)) {
hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm) hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil { if err != nil {
fmt.Printf("unable to open history file: %v\n", err) fmt.Printf("unable to open history file: %v\n", err)
@ -91,7 +155,7 @@ func (self *repl) withHistory(op func(*os.File)) {
hist.Close() hist.Close()
} }
func (self *repl) parseInput(code string) { func (self *jsre) parseInput(code string) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Println("[native] error", r) fmt.Println("[native] error", r)
@ -108,79 +172,21 @@ func (self *repl) parseInput(code string) {
var indentCount = 0 var indentCount = 0
var str = "" var str = ""
func (self *repl) setIndent() { func (self *jsre) setIndent() {
open := strings.Count(str, "{") open := strings.Count(str, "{")
open += strings.Count(str, "(") open += strings.Count(str, "(")
closed := strings.Count(str, "}") closed := strings.Count(str, "}")
closed += strings.Count(str, ")") closed += strings.Count(str, ")")
indentCount = open - closed indentCount = open - closed
if indentCount <= 0 { if indentCount <= 0 {
self.prompt = "> " self.ps1 = "> "
} else { } else {
self.prompt = strings.Join(make([]string, indentCount*2), "..") self.ps1 = strings.Join(make([]string, indentCount*2), "..")
self.prompt += " " self.ps1 += " "
} }
} }
func (self *repl) read(lr *liner.State) { func (self *jsre) printValue(v interface{}) {
for {
input, err := lr.Prompt(self.prompt)
if err != nil {
return
}
if input == "" {
continue
}
str += input + "\n"
self.setIndent()
if indentCount <= 0 {
if input == "exit" {
return
}
hist := str[:len(str)-1]
lr.AppendHistory(hist)
self.parseInput(str)
str = ""
}
}
}
func (self *repl) dumbRead() {
fmt.Println("Unsupported terminal, line editing will not work.")
// process lines
readDone := make(chan struct{})
go func() {
r := bufio.NewReader(os.Stdin)
loop:
for {
fmt.Print(self.prompt)
line, err := r.ReadString('\n')
switch {
case err != nil || line == "exit":
break loop
case line == "":
continue
default:
self.parseInput(line + "\n")
}
}
close(readDone)
}()
// wait for Ctrl-C
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
defer signal.Stop(sigc)
select {
case <-readDone:
case <-sigc:
os.Stdin.Close() // terminate read
}
}
func (self *repl) printValue(v interface{}) {
method, _ := self.re.Vm.Get("prettyPrint") method, _ := self.re.Vm.Get("prettyPrint")
v, err := self.re.Vm.ToValue(v) v, err := self.re.Vm.ToValue(v)
if err == nil { if err == nil {
@ -191,7 +197,7 @@ func (self *repl) printValue(v interface{}) {
} }
} }
func (self *repl) initStdFuncs() { func (self *jsre) initStdFuncs() {
t, _ := self.re.Vm.Get("eth") t, _ := self.re.Vm.Get("eth")
eth := t.Object() eth := t.Object()
eth.Set("connect", self.connect) eth.Set("connect", self.connect)
@ -205,7 +211,7 @@ func (self *repl) initStdFuncs() {
* The following methods are natively implemented javascript functions. * The following methods are natively implemented javascript functions.
*/ */
func (self *repl) dump(call otto.FunctionCall) otto.Value { func (self *jsre) dump(call otto.FunctionCall) otto.Value {
var block *types.Block var block *types.Block
if len(call.ArgumentList) > 0 { if len(call.ArgumentList) > 0 {
@ -236,17 +242,17 @@ func (self *repl) dump(call otto.FunctionCall) otto.Value {
return v return v
} }
func (self *repl) stopMining(call otto.FunctionCall) otto.Value { func (self *jsre) stopMining(call otto.FunctionCall) otto.Value {
self.xeth.Miner().Stop() self.xeth.Miner().Stop()
return otto.TrueValue() return otto.TrueValue()
} }
func (self *repl) startMining(call otto.FunctionCall) otto.Value { func (self *jsre) startMining(call otto.FunctionCall) otto.Value {
self.xeth.Miner().Start() self.xeth.Miner().Start()
return otto.TrueValue() return otto.TrueValue()
} }
func (self *repl) connect(call otto.FunctionCall) otto.Value { func (self *jsre) connect(call otto.FunctionCall) otto.Value {
nodeURL, err := call.Argument(0).ToString() nodeURL, err := call.Argument(0).ToString()
if err != nil { if err != nil {
return otto.FalseValue() return otto.FalseValue()
@ -257,7 +263,7 @@ func (self *repl) connect(call otto.FunctionCall) otto.Value {
return otto.TrueValue() return otto.TrueValue()
} }
func (self *repl) export(call otto.FunctionCall) otto.Value { func (self *jsre) export(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) == 0 { if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name") fmt.Println("err: require file name")
return otto.FalseValue() return otto.FalseValue()

View File

@ -157,11 +157,12 @@ func run(ctx *cli.Context) {
func runjs(ctx *cli.Context) { func runjs(ctx *cli.Context) {
eth := utils.GetEthereum(ClientIdentifier, Version, ctx) eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
startEth(ctx, eth) startEth(ctx, eth)
repl := newJSRE(eth)
if len(ctx.Args()) == 0 { if len(ctx.Args()) == 0 {
runREPL(eth) repl.interactive()
} else { } else {
for _, file := range ctx.Args() { for _, file := range ctx.Args() {
execJsFile(eth, file) repl.exec(file)
} }
} }
eth.Stop() eth.Stop()