Merge branch 'develop' into rpcfrontier

Conflicts:
	rpc/api.go
	rpc/args.go
This commit is contained in:
obscuren
2015-03-11 01:08:42 +01:00
42 changed files with 847 additions and 1564 deletions

View File

@@ -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 repl struct {
type dumbterm struct{ r *bufio.Reader }
func (r dumbterm) Prompt(p string) (string, error) {
fmt.Print(p)
return r.r.ReadString('\n')
}
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 *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.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 += " "
self.ps1 = strings.Join(make([]string, indentCount*2), "..")
self.ps1 += " "
}
}
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
}
}
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()

View File

@@ -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{
@@ -60,6 +62,23 @@ func init() {
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",
@@ -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)
startEth(ctx, eth)
if len(ctx.Args()) == 0 {
runREPL(eth)
eth.Stop()
eth.WaitForShutdown()
} else if len(ctx.Args()) == 1 {
execJsFile(eth, ctx.Args()[0])
} else {
utils.Fatalf("This command can handle at most one argument.")
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 {
repl.interactive()
} else {
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
}