From 9f0e3bd286472f85ab2457fc19cd48cdf12df110 Mon Sep 17 00:00:00 2001
From: Felix Lange <fjl@twurst.com>
Date: Tue, 10 Mar 2015 02:00:57 +0100
Subject: cmd/ethereum: unlock accounts on JS REPL

---
 cmd/ethereum/js.go   | 210 ++++++++++++++++++++++++++-------------------------
 cmd/ethereum/main.go |   5 +-
 2 files changed, 111 insertions(+), 104 deletions(-)

diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go
index e3165d3f5..96e292733 100644
--- a/cmd/ethereum/js.go
+++ b/cmd/ethereum/js.go
@@ -22,11 +22,9 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"os/signal"
 	"path"
 	"strings"
 
-	"github.com/ethereum/go-ethereum/cmd/utils"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/ethutil"
@@ -37,94 +35,96 @@ import (
 	"github.com/peterh/liner"
 )
 
-func execJsFile(ethereum *eth.Ethereum, filename string) {
-	file, err := os.Open(filename)
-	if err != nil {
-		utils.Fatalf("%v", err)
-	}
-	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 prompter interface {
+	AppendHistory(string)
+	Prompt(p string) (string, error)
+	PasswordPrompt(p string) (string, error)
+}
+
+type dumbPrompter struct{ r *bufio.Reader }
+
+func (r dumbPrompter) Prompt(p string) (string, error) {
+	fmt.Print(p)
+	return r.r.ReadString('\n')
 }
 
-type repl struct {
+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
 	ethereum *eth.Ethereum
 	xeth     *xeth.XEth
-	prompt   string
-	lr       *liner.State
+	ps1      string
+	prompter
 }
 
-func runREPL(ethereum *eth.Ethereum) {
-	xeth := xeth.New(ethereum, nil)
-	repl := &repl{
-		re:       javascript.NewJSRE(xeth),
-		xeth:     xeth,
-		ethereum: ethereum,
-		prompt:   "> ",
-	}
-	repl.initStdFuncs()
+func newJSRE(ethereum *eth.Ethereum) *jsre {
+	js := &jsre{ethereum: ethereum, ps1: "> "}
+	js.xeth = xeth.New(ethereum, js)
+	js.re = javascript.NewJSRE(js.xeth)
+	js.initStdFuncs()
+
 	if !liner.TerminalSupported() {
-		repl.dumbRead()
+		js.prompter = dumbPrompter{bufio.NewReader(os.Stdin)}
 	} else {
 		lr := liner.NewLiner()
-		defer lr.Close()
 		lr.SetCtrlCAborts(true)
-		repl.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
-		repl.read(lr)
-		repl.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
+		defer lr.Close()
+		js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
+		defer js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
+		js.prompter = lr
 	}
+	return js
 }
 
-func (self *repl) withHistory(op func(*os.File)) {
-	hist, err := os.OpenFile(path.Join(self.ethereum.DataDir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
-	if err != nil {
-		fmt.Printf("unable to open history file: %v\n", err)
-		return
-	}
-	op(hist)
-	hist.Close()
+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 *repl) parseInput(code string) {
-	defer func() {
-		if r := recover(); r != nil {
-			fmt.Println("[native] error", r)
-		}
-	}()
-	value, err := self.re.Run(code)
+func (self *jsre) UnlockAccount(addr []byte) bool {
+	fmt.Printf("Please unlock account %x.\n", addr)
+	pass, err := self.prompter.PasswordPrompt("Passphrase: ")
 	if err != nil {
-		fmt.Println(err)
-		return
+		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
 	}
-	self.printValue(value)
 }
 
-var indentCount = 0
-var str = ""
-
-func (self *repl) setIndent() {
-	open := strings.Count(str, "{")
-	open += strings.Count(str, "(")
-	closed := strings.Count(str, "}")
-	closed += strings.Count(str, ")")
-	indentCount = open - closed
-	if indentCount <= 0 {
-		self.prompt = "> "
-	} else {
-		self.prompt = strings.Join(make([]string, indentCount*2), "..")
-		self.prompt += " "
+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 *repl) read(lr *liner.State) {
+func (self *jsre) interactive() {
 	for {
-		input, err := lr.Prompt(self.prompt)
+		input, err := self.prompter.Prompt(self.ps1)
 		if err != nil {
 			return
 		}
@@ -138,49 +138,55 @@ func (self *repl) read(lr *liner.State) {
 				return
 			}
 			hist := str[:len(str)-1]
-			lr.AppendHistory(hist)
+			self.prompter.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")
-			}
+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)
+	if err != nil {
+		fmt.Printf("unable to open history file: %v\n", err)
+		return
+	}
+	op(hist)
+	hist.Close()
+}
+
+func (self *jsre) parseInput(code string) {
+	defer func() {
+		if r := recover(); r != nil {
+			fmt.Println("[native] error", r)
 		}
-		close(readDone)
 	}()
+	value, err := self.re.Run(code)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	self.printValue(value)
+}
 
-	// wait for Ctrl-C
-	sigc := make(chan os.Signal, 1)
-	signal.Notify(sigc, os.Interrupt, os.Kill)
-	defer signal.Stop(sigc)
+var indentCount = 0
+var str = ""
 
-	select {
-	case <-readDone:
-	case <-sigc:
-		os.Stdin.Close() // terminate read
+func (self *jsre) setIndent() {
+	open := strings.Count(str, "{")
+	open += strings.Count(str, "(")
+	closed := strings.Count(str, "}")
+	closed += strings.Count(str, ")")
+	indentCount = open - closed
+	if indentCount <= 0 {
+		self.ps1 = "> "
+	} else {
+		self.ps1 = strings.Join(make([]string, indentCount*2), "..")
+		self.ps1 += " "
 	}
 }
 
-func (self *repl) printValue(v interface{}) {
+func (self *jsre) printValue(v interface{}) {
 	method, _ := self.re.Vm.Get("prettyPrint")
 	v, err := self.re.Vm.ToValue(v)
 	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")
 	eth := t.Object()
 	eth.Set("connect", self.connect)
@@ -205,7 +211,7 @@ func (self *repl) initStdFuncs() {
  * 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
 
 	if len(call.ArgumentList) > 0 {
@@ -236,17 +242,17 @@ func (self *repl) dump(call otto.FunctionCall) otto.Value {
 	return v
 }
 
-func (self *repl) stopMining(call otto.FunctionCall) otto.Value {
+func (self *jsre) stopMining(call otto.FunctionCall) otto.Value {
 	self.xeth.Miner().Stop()
 	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()
 	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()
 	if err != nil {
 		return otto.FalseValue()
@@ -257,7 +263,7 @@ func (self *repl) connect(call otto.FunctionCall) otto.Value {
 	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 {
 		fmt.Println("err: require file name")
 		return otto.FalseValue()
diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go
index d0edef81d..1703c02bb 100644
--- a/cmd/ethereum/main.go
+++ b/cmd/ethereum/main.go
@@ -157,11 +157,12 @@ func run(ctx *cli.Context) {
 func runjs(ctx *cli.Context) {
 	eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
 	startEth(ctx, eth)
+	repl := newJSRE(eth)
 	if len(ctx.Args()) == 0 {
-		runREPL(eth)
+		repl.interactive()
 	} else {
 		for _, file := range ctx.Args() {
-			execJsFile(eth, file)
+			repl.exec(file)
 		}
 	}
 	eth.Stop()
-- 
cgit v1.2.3