aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/ethereum
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/ethereum')
-rw-r--r--cmd/ethereum/js.go218
-rw-r--r--cmd/ethereum/main.go141
2 files changed, 232 insertions, 127 deletions
diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go
index e3165d3f5..5432fb9b1 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,51 +35,123 @@ 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 dumbterm struct{ r *bufio.Reader }
+
+func (r dumbterm) Prompt(p string) (string, error) {
+ fmt.Print(p)
+ return r.r.ReadString('\n')
}
-type repl struct {
+func (r dumbterm) 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 dumbterm) AppendHistory(string) {}
+
+type jsre struct {
re *javascript.JSRE
ethereum *eth.Ethereum
xeth *xeth.XEth
- prompt string
- lr *liner.State
+ ps1 string
+ atexit func()
+
+ 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 = dumbterm{bufio.NewReader(os.Stdin)}
} else {
lr := liner.NewLiner()
- defer lr.Close()
+ js.withHistory(func(hist *os.File) { lr.ReadHistory(hist) })
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) })
+ js.prompter = lr
+ js.atexit = func() {
+ js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
+ lr.Close()
+ }
+ }
+ return js
+}
+
+func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool {
+ p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx)
+ answer, _ := self.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.PasswordPrompt("Passphrase: ")
+ if err != nil {
+ return false
+ }
+ // TODO: allow retry
+ if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil {
+ return false
+ } else {
+ fmt.Println("Account is now unlocked for this session.")
+ return true
+ }
+}
+
+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) withHistory(op func(*os.File)) {
+func (self *jsre) interactive() {
+ for {
+ input, err := self.Prompt(self.ps1)
+ if err != nil {
+ break
+ }
+ if input == "" {
+ continue
+ }
+ str += input + "\n"
+ self.setIndent()
+ if indentCount <= 0 {
+ if input == "exit" {
+ break
+ }
+ hist := str[:len(str)-1]
+ self.AppendHistory(hist)
+ self.parseInput(str)
+ str = ""
+ }
+ }
+ if self.atexit != nil {
+ self.atexit()
+ }
+}
+
+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)
@@ -91,7 +161,7 @@ func (self *repl) withHistory(op func(*os.File)) {
hist.Close()
}
-func (self *repl) parseInput(code string) {
+func (self *jsre) parseInput(code string) {
defer func() {
if r := recover(); r != nil {
fmt.Println("[native] error", r)
@@ -108,79 +178,21 @@ func (self *repl) parseInput(code string) {
var indentCount = 0
var str = ""
-func (self *repl) setIndent() {
+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.prompt = "> "
+ self.ps1 = "> "
} else {
- self.prompt = strings.Join(make([]string, indentCount*2), "..")
- self.prompt += " "
- }
-}
-
-func (self *repl) read(lr *liner.State) {
- 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
+ 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 +203,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 +217,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 +248,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 +269,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 12e205f37..c9f620142 100644
--- a/cmd/ethereum/main.go
+++ b/cmd/ethereum/main.go
@@ -21,19 +21,23 @@
package main
import (
+ "bufio"
"fmt"
"os"
"runtime"
"strconv"
+ "strings"
"time"
"github.com/codegangsta/cli"
+ "github.com/ethereum/go-ethereum/accounts"
"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"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state"
+ "github.com/peterh/liner"
)
const (
@@ -43,12 +47,10 @@ const (
var (
clilogger = logger.NewLogger("CLI")
- app = cli.NewApp()
+ app = utils.NewApp(Version, "the go-ethereum command line interface")
)
func init() {
- app.Version = Version
- app.Usage = "the go-ethereum command-line client"
app.Action = run
app.HideVersion = true // we have a command to print the version
app.Commands = []cli.Command{
@@ -61,6 +63,23 @@ The output of this command is supposed to be machine-readable.
`,
},
{
+ Action: accountList,
+ Name: "account",
+ Usage: "manage accounts",
+ Subcommands: []cli.Command{
+ {
+ Action: accountList,
+ Name: "list",
+ Usage: "print account addresses",
+ },
+ {
+ Action: accountCreate,
+ Name: "new",
+ Usage: "create a new account",
+ },
+ },
+ },
+ {
Action: dump,
Name: "dump",
Usage: `dump a specific block from storage`,
@@ -93,13 +112,10 @@ runtime will execute the file and exit.
Usage: `export blockchain into file`,
},
}
- app.Author = ""
- app.Email = ""
app.Flags = []cli.Flag{
+ utils.UnlockedAccountFlag,
utils.BootnodesFlag,
utils.DataDirFlag,
- utils.KeyRingFlag,
- utils.KeyStoreFlag,
utils.ListenPortFlag,
utils.LogFileFlag,
utils.LogFormatFlag,
@@ -140,38 +156,100 @@ func main() {
func run(ctx *cli.Context) {
fmt.Printf("Welcome to the FRONTIER\n")
utils.HandleInterrupt()
- eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
+ eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx)
+ if err == accounts.ErrNoKeys {
+ utils.Fatalf(`No accounts configured.
+Please run 'ethereum account new' to create a new account.`)
+ } else if err != nil {
+ utils.Fatalf("%v", err)
+ }
+
startEth(ctx, eth)
// this blocks the thread
eth.WaitForShutdown()
}
func runjs(ctx *cli.Context) {
- eth := utils.GetEthereum(ClientIdentifier, Version, ctx)
+ eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx)
+ if err == accounts.ErrNoKeys {
+ utils.Fatalf(`No accounts configured.
+Please run 'ethereum account new' to create a new account.`)
+ } else if err != nil {
+ utils.Fatalf("%v", err)
+ }
+
startEth(ctx, eth)
+ repl := newJSRE(eth)
if len(ctx.Args()) == 0 {
- runREPL(eth)
- eth.Stop()
- eth.WaitForShutdown()
- } else if len(ctx.Args()) == 1 {
- execJsFile(eth, ctx.Args()[0])
+ repl.interactive()
} else {
- utils.Fatalf("This command can handle at most one argument.")
+ for _, file := range ctx.Args() {
+ repl.exec(file)
+ }
}
+ eth.Stop()
+ eth.WaitForShutdown()
}
func startEth(ctx *cli.Context, eth *eth.Ethereum) {
utils.StartEthereum(eth)
+
+ // Load startup keys. XXX we are going to need a different format
+ account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
+ if len(account) > 0 {
+ split := strings.Split(account, ":")
+ if len(split) != 2 {
+ utils.Fatalf("Illegal 'unlock' format (address:password)")
+ }
+ am := utils.GetAccountManager(ctx)
+ // Attempt to unlock the account
+ err := am.Unlock(ethutil.Hex2Bytes(split[0]), split[1])
+ if err != nil {
+ utils.Fatalf("Unlock account failed '%v'", err)
+ }
+ }
+ // Start auxiliary services if enabled.
if ctx.GlobalBool(utils.RPCEnabledFlag.Name) {
- addr := ctx.GlobalString(utils.RPCListenAddrFlag.Name)
- port := ctx.GlobalInt(utils.RPCPortFlag.Name)
- utils.StartRpc(eth, addr, port)
+ utils.StartRPC(eth, ctx)
}
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
eth.Miner().Start()
}
}
+func accountList(ctx *cli.Context) {
+ am := utils.GetAccountManager(ctx)
+ accts, err := am.Accounts()
+ if err != nil {
+ utils.Fatalf("Could not list accounts: %v", err)
+ }
+ for _, acct := range accts {
+ fmt.Printf("Address: %#x\n", acct)
+ }
+}
+
+func accountCreate(ctx *cli.Context) {
+ am := utils.GetAccountManager(ctx)
+ fmt.Println("The new account will be encrypted with a passphrase.")
+ fmt.Println("Please enter a passphrase now.")
+ auth, err := readPassword("Passphrase: ", true)
+ if err != nil {
+ utils.Fatalf("%v", err)
+ }
+ confirm, err := readPassword("Repeat Passphrase: ", false)
+ if err != nil {
+ utils.Fatalf("%v", err)
+ }
+ if auth != confirm {
+ utils.Fatalf("Passphrases did not match.")
+ }
+ acct, err := am.NewAccount(auth)
+ if err != nil {
+ utils.Fatalf("Could not create the account: %v", err)
+ }
+ fmt.Printf("Address: %#x\n", acct.Address)
+}
+
func importchain(ctx *cli.Context) {
if len(ctx.Args()) != 1 {
utils.Fatalf("This command requires an argument.")
@@ -221,12 +299,6 @@ func dump(ctx *cli.Context) {
}
}
-// hashish returns true for strings that look like hashes.
-func hashish(x string) bool {
- _, err := strconv.Atoi(x)
- return err != nil
-}
-
func version(c *cli.Context) {
fmt.Printf(`%v
Version: %v
@@ -238,3 +310,24 @@ GOPATH=%s
GOROOT=%s
`, ClientIdentifier, Version, eth.ProtocolVersion, eth.NetworkId, runtime.Version(), runtime.GOOS, os.Getenv("GOPATH"), runtime.GOROOT())
}
+
+// hashish returns true for strings that look like hashes.
+func hashish(x string) bool {
+ _, err := strconv.Atoi(x)
+ return err != nil
+}
+
+func readPassword(prompt string, warnTerm bool) (string, error) {
+ if liner.TerminalSupported() {
+ lr := liner.NewLiner()
+ defer lr.Close()
+ return lr.PasswordPrompt(prompt)
+ }
+ if warnTerm {
+ fmt.Println("!! Unsupported terminal, password will be echoed.")
+ }
+ fmt.Print(prompt)
+ input, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ fmt.Println()
+ return input, err
+}