From bc45e5c6de3052a4c853387dea0af5cd9207f1f7 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Thu, 26 Feb 2015 13:22:09 +0100 Subject: Integrate eth_accounts and eth_transact to use new account manager * Add from to eth_transact / xeth.Transact and add static pass in lieu of integrating with native Mist window for user passphrase entry * Make eth_accounts return AccountManager.Accounts() * Add a Generate Key menu item in Mist --- cmd/mist/assets/qml/main.qml | 5 +++++ cmd/mist/bindings.go | 4 ++-- cmd/mist/gui.go | 7 +++++++ cmd/mist/ui_lib.go | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml index f9ee6939d..7f72d35f4 100644 --- a/cmd/mist/assets/qml/main.qml +++ b/cmd/mist/assets/qml/main.qml @@ -190,6 +190,11 @@ ApplicationWindow { } } + MenuItem { + text: "Generate key" + shortcut: "Ctrl+k" + onTriggered: gui.generateKey() + } } Menu { diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index f21aa3135..fd89eb7e2 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -49,7 +49,7 @@ func (gui *Gui) LogPrint(level logger.LogLevel, msg string) { } */ } -func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, error) { +func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) { var data string if len(recipient) == 0 { code, err := ethutil.Compile(d, false) @@ -61,7 +61,7 @@ func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (string, err data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) } - return gui.xeth.Transact(recipient, value, gas, gasPrice, data) + return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } // functions that allow Gui to implement interface guilogger.LogSystem diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index 869b689dd..bc6e9ed53 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -175,6 +175,13 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { func (gui *Gui) ImportKey(filePath string) { } +func (gui *Gui) GenerateKey() { + _, err := gui.eth.AccountManager().NewAccount("hurr") + if err != nil { + // TODO: UI feedback? + } +} + func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) { context.SetVar("lib", gui) component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml")) diff --git a/cmd/mist/ui_lib.go b/cmd/mist/ui_lib.go index 098e8fca5..af78f0c10 100644 --- a/cmd/mist/ui_lib.go +++ b/cmd/mist/ui_lib.go @@ -171,6 +171,7 @@ func (self *UiLib) Transact(params map[string]interface{}) (string, error) { object := mapToTxParams(params) return self.XEth.Transact( + object["from"], object["to"], object["value"], object["gas"], -- cgit v1.2.3 From a2810c06d7cfc64e1636fe4ecfd5e35cc52b0d2b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 7 Mar 2015 12:39:52 +0100 Subject: cmd/ethereum: add account commands --- cmd/ethereum/main.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++------ cmd/utils/flags.go | 48 +++++++++++++++---------------- 2 files changed, 93 insertions(+), 34 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1133bd6f7..f12616e17 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -21,6 +21,7 @@ package main import ( + "bufio" "fmt" "os" "runtime" @@ -34,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/state" + "github.com/peterh/liner" ) const ( @@ -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,8 +112,6 @@ runtime will execute the file and exit. app.Flags = []cli.Flag{ utils.BootnodesFlag, utils.DataDirFlag, - utils.KeyRingFlag, - utils.KeyStoreFlag, utils.ListenPortFlag, utils.LogFileFlag, utils.LogFormatFlag, @@ -166,6 +183,37 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) { } } +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) + 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.") @@ -201,12 +249,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 %v PV=%d @@ -216,3 +258,24 @@ GOPATH=%s GOROOT=%s `, ClientIdentifier, Version, eth.ProtocolVersion, runtime.GOOS, runtime.Version(), 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 +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fb80ac708..4f3ecd2b2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -4,8 +4,10 @@ import ( "crypto/ecdsa" "path" "runtime" + "time" "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -30,16 +32,6 @@ var ( Name: "vm", Usage: "Virtual Machine type: 0 is standard VM, 1 is debug VM", } - KeyRingFlag = cli.StringFlag{ - Name: "keyring", - Usage: "Name of keyring to be used", - Value: "", - } - KeyStoreFlag = cli.StringFlag{ - Name: "keystore", - Usage: `Where to store keyrings: "db" or "file"`, - Value: "db", - } DataDirFlag = cli.StringFlag{ Name: "datadir", Usage: "Data directory to be used", @@ -145,22 +137,20 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(clientID, version), - KeyStore: ctx.GlobalString(KeyStoreFlag.Name), - DataDir: ctx.GlobalString(DataDirFlag.Name), - LogFile: ctx.GlobalString(LogFileFlag.Name), - LogLevel: ctx.GlobalInt(LogLevelFlag.Name), - LogFormat: ctx.GlobalString(LogFormatFlag.Name), - MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), - - MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), - Port: ctx.GlobalString(ListenPortFlag.Name), - NAT: GetNAT(ctx), - NodeKey: GetNodeKey(ctx), - KeyRing: ctx.GlobalString(KeyRingFlag.Name), - Shh: true, - Dial: true, - BootNodes: ctx.GlobalString(BootnodesFlag.Name), + Name: p2p.MakeName(clientID, version), + DataDir: ctx.GlobalString(DataDirFlag.Name), + LogFile: ctx.GlobalString(LogFileFlag.Name), + LogLevel: ctx.GlobalInt(LogLevelFlag.Name), + LogFormat: ctx.GlobalString(LogFormatFlag.Name), + MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name), + AccountManager: GetAccountManager(ctx), + MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), + Port: ctx.GlobalString(ListenPortFlag.Name), + NAT: GetNAT(ctx), + NodeKey: GetNodeKey(ctx), + Shh: true, + Dial: true, + BootNodes: ctx.GlobalString(BootnodesFlag.Name), }) if err != nil { exit(err) @@ -176,3 +166,9 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database) { } return core.NewChainManager(db, new(event.TypeMux)), db } + +func GetAccountManager(ctx *cli.Context) *accounts.AccountManager { + dataDir := ctx.GlobalString(DataDirFlag.Name) + ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) + return accounts.NewAccountManager(ks, 300*time.Second) +} -- cgit v1.2.3 From fb53a9362e1238d8edb466d77427dc3cbb13eb20 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sun, 8 Mar 2015 01:52:49 +0100 Subject: accounts: AccountManager -> Manager --- cmd/utils/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4f3ecd2b2..e6eee20a7 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -167,8 +167,8 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database) { return core.NewChainManager(db, new(event.TypeMux)), db } -func GetAccountManager(ctx *cli.Context) *accounts.AccountManager { +func GetAccountManager(ctx *cli.Context) *accounts.Manager { dataDir := ctx.GlobalString(DataDirFlag.Name) ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) - return accounts.NewAccountManager(ks, 300*time.Second) + return accounts.NewManager(ks, 300*time.Second) } -- cgit v1.2.3 From 2dacb51fb02816c7591bfdbaaf7a160e122e5590 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 16:58:06 +0100 Subject: cmd/blocktest: simplify to fix build block tests only need the chain, not all of ethereum. --- cmd/blocktest/flags.go | 41 ----------- cmd/blocktest/main.go | 182 +++++++++---------------------------------------- 2 files changed, 34 insertions(+), 189 deletions(-) delete mode 100644 cmd/blocktest/flags.go (limited to 'cmd') diff --git a/cmd/blocktest/flags.go b/cmd/blocktest/flags.go deleted file mode 100644 index c811e5b85..000000000 --- a/cmd/blocktest/flags.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -/** - * @authors - * Gustav Simonsson - */ -package main - -import ( - "flag" - "fmt" - "os" -) - -var ( - TestFile string -) - -func Init() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) - flag.PrintDefaults() - } - flag.Parse() - - TestFile = flag.Arg(0) -} diff --git a/cmd/blocktest/main.go b/cmd/blocktest/main.go index 4a05b8bee..b96f42710 100644 --- a/cmd/blocktest/main.go +++ b/cmd/blocktest/main.go @@ -25,34 +25,26 @@ package main import ( "bytes" - "crypto/ecdsa" "encoding/hex" "encoding/json" + "flag" "fmt" "io/ioutil" "log" "math/big" - "path" + "os" "runtime" - "strconv" "strings" - "time" "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/ethutil" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rlp" ) -const ( - ClientIdentifier = "Ethereum(G)" - Version = "0.8.6" -) - type Account struct { Balance string Code string @@ -78,6 +70,7 @@ type BlockHeader struct { TransactionsTrie string UncleHash string } + type Tx struct { Data string GasLimit string @@ -103,103 +96,40 @@ type Test struct { Pre map[string]Account } -var ( - Identifier string - KeyRing string - DiffTool bool - DiffType string - KeyStore string - StartRpc bool - StartWebSockets bool - RpcListenAddress string - RpcPort int - WsPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - LogFormat string - Dump bool - DumpHash string - DumpNumber int - VmType int - ImportChain string - SHH bool - Dial bool - PrintVersion bool - MinerThreads int -) - -// flags specific to cli client -var ( - StartMining bool - StartJsConsole bool - InputFile string -) - func main() { - init_vars() + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() - Init() + runtime.GOMAXPROCS(runtime.NumCPU()) + logger.AddLogSystem(logger.NewStdLogSystem(os.Stderr, log.LstdFlags, logger.DebugDetailLevel)) + defer func() { logger.Flush() }() - if len(TestFile) < 1 { - log.Fatal("Please specify test file") + if len(os.Args) < 2 { + utils.Fatalf("Please specify a test file as the first argument.") } - blocks, err := loadBlocksFromTestFile(TestFile) + blocks, err := loadBlocksFromTestFile(os.Args[1]) if err != nil { - panic(err) + utils.Fatalf("Could not load blocks: %v", err) } - runtime.GOMAXPROCS(runtime.NumCPU()) + chain := memchain() + chain.ResetWithGenesisBlock(blocks[0]) + if err = chain.InsertChain(types.Blocks{blocks[1]}); err != nil { + utils.Fatalf("Error: %v", err) + } else { + fmt.Println("PASS") + } +} - defer func() { - logger.Flush() - }() - - utils.HandleInterrupt() - - utils.InitConfig(VmType, ConfigFile, Datadir, "ethblocktest") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - LogFormat: LogFormat, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - KeyRing: KeyRing, - Shh: true, - Dial: Dial, - BootNodes: BootNodes, - NodeKey: NodeKey, - MinerThreads: MinerThreads, - }) - - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) - utils.StartEthereum(ethereum) - - ethereum.ChainManager().ResetWithGenesisBlock(blocks[0]) - - // fmt.Println("HURR: ", hex.EncodeToString(ethutil.Encode(blocks[0].RlpData()))) - - go ethereum.ChainManager().InsertChain(types.Blocks{blocks[1]}) - fmt.Println("OK! ") - ethereum.WaitForShutdown() +func memchain() *core.ChainManager { + db, err := ethdb.NewMemDatabase() + if err != nil { + utils.Fatalf("Could not create in-memory database: %v", err) + } + return core.NewChainManager(db, new(event.TypeMux)) } func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { @@ -207,9 +137,8 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { if err != nil { return } - bt := *new(map[string]Test) - err = json.Unmarshal(fileContent, &bt) - if err != nil { + bt := make(map[string]Test) + if err = json.Unmarshal(fileContent, &bt); err != nil { return } @@ -272,49 +201,6 @@ func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { return } -func init_vars() { - VmType = 0 - Identifier = "" - KeyRing = "" - KeyStore = "db" - RpcListenAddress = "127.0.0.1" - RpcPort = 8545 - WsPort = 40404 - StartRpc = true - StartWebSockets = false - NonInteractive = false - GenAddr = false - SecretFile = "" - ExportDir = "" - LogFile = "" - - timeStr := strconv.FormatInt(time.Now().UnixNano(), 10) - - Datadir = path.Join(ethutil.DefaultDataDir(), timeStr) - ConfigFile = path.Join(ethutil.DefaultDataDir(), timeStr, "conf.ini") - - DebugFile = "" - LogLevel = 5 - LogFormat = "std" - DiffTool = false - DiffType = "all" - ShowGenesis = false - ImportChain = "" - Dump = false - DumpHash = "" - DumpNumber = -1 - StartMining = false - StartJsConsole = false - PrintVersion = false - MinerThreads = runtime.NumCPU() - - Dial = false - OutboundPort = "30303" - BootNodes = "" - MaxPeer = 1 - -} - func hex_decode(s string) (res []byte, err error) { return hex.DecodeString(strings.TrimPrefix(s, "0x")) } -- cgit v1.2.3 From 73d1ebe244644c2d74a1c0c38a3b339e72140886 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 22:51:50 +0100 Subject: cmd/utils: add NewApp --- cmd/ethereum/main.go | 6 +----- cmd/utils/flags.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index f12616e17..f5215c587 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -45,12 +45,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{ @@ -107,8 +105,6 @@ runtime will execute the file and exit. Usage: `import a blockchain file`, }, } - app.Author = "" - app.Email = "" app.Flags = []cli.Flag{ utils.BootnodesFlag, utils.DataDirFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e6eee20a7..2156963c4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,6 +19,17 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" ) +// NewApp creates an app with sane defaults. +func NewApp(version, usage string) *cli.App { + app := cli.NewApp() + app.Name = path.Base(os.Args[0]) + app.Author = "" + app.Email = "" + app.Version = version + app.Usage = usage + return app +} + // These are all the command line flags we support. // If you add to this list, please remember to include the // flag in the appropriate command definition. -- cgit v1.2.3 From a11f1d6a7ec2eaa1a348776072c49019368a5ef3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:00:27 +0100 Subject: rpc: add dataDir parameter and JSON-RPC handler --- cmd/ethereum/main.go | 4 +--- cmd/utils/cmd.go | 12 ------------ cmd/utils/flags.go | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+), 15 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index f5215c587..4855a3e4a 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -170,9 +170,7 @@ func runjs(ctx *cli.Context) { func startEth(ctx *cli.Context, eth *eth.Ethereum) { utils.StartEthereum(eth) 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() diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 3c3d3955d..79ae7888a 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -34,9 +34,7 @@ import ( "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/rlp" - rpchttp "github.com/ethereum/go-ethereum/rpc/http" "github.com/ethereum/go-ethereum/state" - "github.com/ethereum/go-ethereum/xeth" ) var clilogger = logger.NewLogger("CLI") @@ -165,16 +163,6 @@ func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, Secre clilogger.Infof("Main address %x\n", keyManager.Address()) } -func StartRpc(ethereum *eth.Ethereum, RpcListenAddress string, RpcPort int) { - var err error - ethereum.RpcServer, err = rpchttp.NewRpcHttpServer(xeth.New(ethereum), RpcListenAddress, RpcPort) - if err != nil { - clilogger.Errorf("Could not start RPC interface (port %v): %v", RpcPort, err) - } else { - go ethereum.RpcServer.Start() - } -} - func FormatTransactionData(data string) []byte { d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) { slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2156963c4..2995ebad8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,6 +2,10 @@ package utils import ( "crypto/ecdsa" + "fmt" + "net" + "net/http" + "os" "path" "runtime" "time" @@ -17,6 +21,8 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/xeth" ) // NewApp creates an app with sane defaults. @@ -183,3 +189,15 @@ func GetAccountManager(ctx *cli.Context) *accounts.Manager { ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) return accounts.NewManager(ks, 300*time.Second) } + +func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { + addr := ctx.GlobalString(RPCListenAddrFlag.Name) + port := ctx.GlobalInt(RPCPortFlag.Name) + dataDir := ctx.GlobalString(DataDirFlag.Name) + + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) + if err != nil { + Fatalf("Can't listen on %s:%d: %v", addr, port, err) + } + go http.Serve(l, rpc.JSONRPC(xeth.New(eth), dataDir)) +} -- cgit v1.2.3 From 697f6748b8df6b50157f81eb7959d856d3eb688e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:03:20 +0100 Subject: cmd/mist: use cli library and package accounts --- cmd/mist/assets/qml/views/info.qml | 41 ------------ cmd/mist/bindings.go | 29 --------- cmd/mist/debugger.go | 8 ++- cmd/mist/flags.go | 130 ------------------------------------- cmd/mist/gui.go | 53 ++------------- cmd/mist/main.go | 101 ++++++++++++++-------------- 6 files changed, 58 insertions(+), 304 deletions(-) delete mode 100644 cmd/mist/flags.go (limited to 'cmd') diff --git a/cmd/mist/assets/qml/views/info.qml b/cmd/mist/assets/qml/views/info.qml index b2d2f521c..0187bba6d 100644 --- a/cmd/mist/assets/qml/views/info.qml +++ b/cmd/mist/assets/qml/views/info.qml @@ -54,7 +54,6 @@ Rectangle { height: 200 anchors { left: parent.left - right: logLevelSlider.left bottom: parent.bottom top: parent.top } @@ -107,46 +106,6 @@ Rectangle { } } } - - /* - TableView { - id: logView - headerVisible: false - anchors { - right: logLevelSlider.left - left: parent.left - bottom: parent.bottom - top: parent.top - } - - TableViewColumn{ role: "description" ; title: "log" } - - model: logModel - } - */ - - Slider { - id: logLevelSlider - value: gui.getLogLevelInt() - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - - rightMargin: 5 - leftMargin: 5 - topMargin: 5 - bottomMargin: 5 - } - - orientation: Qt.Vertical - maximumValue: 5 - stepSize: 1 - - onValueChanged: { - gui.setLogLevel(value) - } - } } property var logModel: ListModel { diff --git a/cmd/mist/bindings.go b/cmd/mist/bindings.go index fd89eb7e2..b473cc985 100644 --- a/cmd/mist/bindings.go +++ b/cmd/mist/bindings.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/state" ) @@ -37,18 +36,6 @@ type plugin struct { Path string `json:"path"` } -// LogPrint writes to the GUI log. -func (gui *Gui) LogPrint(level logger.LogLevel, msg string) { - /* - str := strings.TrimRight(s, "\n") - lines := strings.Split(str, "\n") - - view := gui.getObjectByName("infoView") - for _, line := range lines { - view.Call("addLog", line) - } - */ -} func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (string, error) { var data string if len(recipient) == 0 { @@ -64,17 +51,6 @@ func (gui *Gui) Transact(from, recipient, value, gas, gasPrice, d string) (strin return gui.xeth.Transact(from, recipient, value, gas, gasPrice, data) } -// functions that allow Gui to implement interface guilogger.LogSystem -func (gui *Gui) SetLogLevel(level logger.LogLevel) { - gui.logLevel = level - gui.eth.Logger().SetLogLevel(level) - gui.config.Save("loglevel", level) -} - -func (gui *Gui) GetLogLevel() logger.LogLevel { - return gui.logLevel -} - func (self *Gui) AddPlugin(pluginPath string) { self.plugins[pluginPath] = plugin{Name: pluginPath, Path: pluginPath} @@ -89,11 +65,6 @@ func (self *Gui) RemovePlugin(pluginPath string) { ethutil.WriteFile(self.eth.DataDir+"/plugins.json", json) } -// this extra function needed to give int typecast value to gui widget -// that sets initial loglevel to default -func (gui *Gui) GetLogLevelInt() int { - return int(gui.logLevel) -} func (self *Gui) DumpState(hash, path string) { var stateDump []byte diff --git a/cmd/mist/debugger.go b/cmd/mist/debugger.go index c1ab2f3f1..bd8ddde37 100644 --- a/cmd/mist/debugger.go +++ b/cmd/mist/debugger.go @@ -137,16 +137,18 @@ func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, data return } + // TODO: improve this + allAccounts, _ := self.lib.eth.AccountManager().Accounts() + var ( gas = ethutil.Big(gasStr) gasPrice = ethutil.Big(gasPriceStr) value = ethutil.Big(valueStr) - // Contract addr as test address - keyPair = self.lib.eth.KeyManager().KeyPair() + acc = allAccounts[0] ) statedb := self.lib.eth.ChainManager().TransState() - account := self.lib.eth.ChainManager().TransState().GetAccount(keyPair.Address()) + account := self.lib.eth.ChainManager().TransState().GetAccount(acc.Address) contract := statedb.NewStateObject([]byte{0}) contract.SetCode(script) contract.SetBalance(value) diff --git a/cmd/mist/flags.go b/cmd/mist/flags.go deleted file mode 100644 index 139af5923..000000000 --- a/cmd/mist/flags.go +++ /dev/null @@ -1,130 +0,0 @@ -/* - This file is part of go-ethereum - - go-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - go-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with go-ethereum. If not, see . -*/ -/** - * @authors - * Jeffrey Wilcke - */ -package main - -import ( - "crypto/ecdsa" - "flag" - "fmt" - "log" - "os" - "path" - "runtime" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethutil" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/vm" -) - -var ( - Identifier string - KeyRing string - KeyStore string - StartRpc bool - RpcListenAddress string - RpcPort int - OutboundPort string - ShowGenesis bool - AddPeer string - MaxPeer int - GenAddr bool - BootNodes string - NodeKey *ecdsa.PrivateKey - NAT nat.Interface - SecretFile string - ExportDir string - NonInteractive bool - Datadir string - LogFile string - ConfigFile string - DebugFile string - LogLevel int - VmType int - MinerThreads int -) - -// flags specific to gui client -var AssetPath string -var defaultConfigFile = path.Join(ethutil.DefaultDataDir(), "conf.ini") - -func Init() { - // TODO: move common flag processing to cmd/utils - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "%s [options] [filename]:\noptions precedence: default < config file < environment variables < command line\n", os.Args[0]) - flag.PrintDefaults() - } - - flag.IntVar(&VmType, "vm", 0, "Virtual Machine type: 0-1: standard, debug") - flag.StringVar(&Identifier, "id", "", "Custom client identifier") - flag.StringVar(&KeyRing, "keyring", "", "identifier for keyring to use") - flag.StringVar(&KeyStore, "keystore", "db", "system to store keyrings: db|file") - flag.StringVar(&RpcListenAddress, "rpcaddr", "127.0.0.1", "address for json-rpc server to listen on") - flag.IntVar(&RpcPort, "rpcport", 8545, "port to start json-rpc server on") - flag.BoolVar(&StartRpc, "rpc", true, "start rpc server") - flag.BoolVar(&NonInteractive, "y", false, "non-interactive mode (say yes to confirmations)") - flag.BoolVar(&GenAddr, "genaddr", false, "create a new priv/pub key") - flag.StringVar(&SecretFile, "import", "", "imports the file given (hex or mnemonic formats)") - flag.StringVar(&ExportDir, "export", "", "exports the session keyring to files in the directory given") - flag.StringVar(&LogFile, "logfile", "", "log file (defaults to standard output)") - flag.StringVar(&Datadir, "datadir", ethutil.DefaultDataDir(), "specifies the datadir to use") - flag.StringVar(&ConfigFile, "conf", defaultConfigFile, "config file") - flag.StringVar(&DebugFile, "debug", "", "debug file (no debugging if not set)") - flag.IntVar(&LogLevel, "loglevel", int(logger.InfoLevel), "loglevel: 0-5 (= silent,error,warn,info,debug,debug detail)") - - flag.StringVar(&AssetPath, "asset_path", ethutil.DefaultAssetPath(), "absolute path to GUI assets directory") - - // Network stuff - var ( - nodeKeyFile = flag.String("nodekey", "", "network private key file") - nodeKeyHex = flag.String("nodekeyhex", "", "network private key (for testing)") - natstr = flag.String("nat", "any", "port mapping mechanism (any|none|upnp|pmp|extip:)") - ) - flag.StringVar(&OutboundPort, "port", "30303", "listening port") - flag.StringVar(&BootNodes, "bootnodes", "", "space-separated node URLs for discovery bootstrap") - flag.IntVar(&MaxPeer, "maxpeer", 30, "maximum desired peers") - - flag.IntVar(&MinerThreads, "minerthreads", runtime.NumCPU(), "number of miner threads") - - flag.Parse() - - var err error - if NAT, err = nat.Parse(*natstr); err != nil { - log.Fatalf("-nat: %v", err) - } - switch { - case *nodeKeyFile != "" && *nodeKeyHex != "": - log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive") - case *nodeKeyFile != "": - if NodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil { - log.Fatalf("-nodekey: %v", err) - } - case *nodeKeyHex != "": - if NodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil { - log.Fatalf("-nodekeyhex: %v", err) - } - } - - if VmType >= int(vm.MaxVmTy) { - log.Fatal("Invalid VM type ", VmType) - } -} diff --git a/cmd/mist/gui.go b/cmd/mist/gui.go index bc6e9ed53..e21d6fcea 100644 --- a/cmd/mist/gui.go +++ b/cmd/mist/gui.go @@ -23,7 +23,6 @@ package main import "C" import ( - "bytes" "encoding/json" "fmt" "io/ioutil" @@ -70,20 +69,18 @@ type Gui struct { txDb *ethdb.LDBDatabase - logLevel logger.LogLevel - open bool + open bool xeth *xeth.XEth Session string - config *ethutil.ConfigManager plugins map[string]plugin } // Create GUI, but doesn't start it -func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session string, logLevel int) *Gui { - db, err := ethdb.NewLDBDatabase("tx_database") +func NewWindow(ethereum *eth.Ethereum) *Gui { + db, err := ethdb.NewLDBDatabase(path.Join(ethereum.DataDir, "tx_database")) if err != nil { panic(err) } @@ -92,10 +89,7 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, session st gui := &Gui{eth: ethereum, txDb: db, xeth: xeth, - logLevel: logger.LogLevel(logLevel), - Session: session, open: false, - config: config, plugins: make(map[string]plugin), serviceEvents: make(chan ServEv, 1), } @@ -142,18 +136,12 @@ func (gui *Gui) Start(assetPath string) { gui.open = true win.Show() - // only add the gui guilogger after window is shown otherwise slider wont be shown - logger.AddLogSystem(gui) win.Wait() - - // need to silence gui guilogger after window closed otherwise logsystem hangs (but do not save loglevel) - gui.logLevel = logger.Silence gui.open = false } func (gui *Gui) Stop() { if gui.open { - gui.logLevel = logger.Silence gui.open = false gui.win.Hide() } @@ -172,9 +160,6 @@ func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) { return gui.win, nil } -func (gui *Gui) ImportKey(filePath string) { -} - func (gui *Gui) GenerateKey() { _, err := gui.eth.AccountManager().NewAccount("hurr") if err != nil { @@ -198,31 +183,11 @@ func (gui *Gui) createWindow(comp qml.Object) *qml.Window { return gui.win } -func (gui *Gui) ImportAndSetPrivKey(secret string) bool { - err := gui.eth.KeyManager().InitFromString(gui.Session, 0, secret) - if err != nil { - guilogger.Errorln("unable to import: ", err) - return false - } - guilogger.Errorln("successfully imported: ", err) - return true -} - -func (gui *Gui) CreateAndSetPrivKey() (string, string, string, string) { - err := gui.eth.KeyManager().Init(gui.Session, 0, true) - if err != nil { - guilogger.Errorln("unable to create key: ", err) - return "", "", "", "" - } - return gui.eth.KeyManager().KeyPair().AsStrings() -} - func (gui *Gui) setInitialChain(ancientBlocks bool) { sBlk := gui.eth.ChainManager().LastBlockHash() blk := gui.eth.ChainManager().GetBlock(sBlk) for ; blk != nil; blk = gui.eth.ChainManager().GetBlock(sBlk) { sBlk = blk.ParentHash() - gui.processBlock(blk, true) } } @@ -266,10 +231,8 @@ func (self *Gui) loadMergedMiningOptions() { } func (gui *Gui) insertTransaction(window string, tx *types.Transaction) { - addr := gui.address() - var inout string - if bytes.Compare(tx.From(), addr) == 0 { + if gui.eth.AccountManager().HasAccount(tx.From()) { inout = "send" } else { inout = "recv" @@ -487,14 +450,6 @@ func (gui *Gui) setPeerInfo() { } } -func (gui *Gui) privateKey() string { - return ethutil.Bytes2Hex(gui.eth.KeyManager().PrivateKey()) -} - -func (gui *Gui) address() []byte { - return gui.eth.KeyManager().Address() -} - /* func LoadExtension(path string) (uintptr, error) { lib, err := ffi.NewLibrary(path) diff --git a/cmd/mist/main.go b/cmd/mist/main.go index 1d4403848..425630ece 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -26,10 +26,10 @@ import ( "runtime" "time" + "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/ui/qt/webengine" "github.com/obscuren/qml" ) @@ -39,56 +39,32 @@ const ( Version = "0.8.6" ) -var ethereum *eth.Ethereum -var mainlogger = logger.NewLogger("MAIN") - -func run() error { - webengine.Initialize() - - // precedence: code-internal flag default < config file < environment variables < command line - Init() // parsing command line - - tstart := time.Now() - config := utils.InitConfig(VmType, ConfigFile, Datadir, "ETH") - - ethereum, err := eth.New(ð.Config{ - Name: p2p.MakeName(ClientIdentifier, Version), - KeyStore: KeyStore, - DataDir: Datadir, - LogFile: LogFile, - LogLevel: LogLevel, - MaxPeers: MaxPeer, - Port: OutboundPort, - NAT: NAT, - Shh: true, - BootNodes: BootNodes, - NodeKey: NodeKey, - KeyRing: KeyRing, - Dial: true, - MinerThreads: MinerThreads, - }) - if err != nil { - mainlogger.Fatalln(err) +var ( + app = utils.NewApp(Version, "the ether browser") + assetPathFlag = cli.StringFlag{ + Name: "asset_path", + Usage: "absolute path to GUI assets directory", + Value: ethutil.DefaultAssetPath(), } - utils.KeyTasks(ethereum.KeyManager(), KeyRing, GenAddr, SecretFile, ExportDir, NonInteractive) +) - if StartRpc { - utils.StartRpc(ethereum, RpcListenAddress, RpcPort) +func init() { + app.Action = run + app.Flags = []cli.Flag{ + assetPathFlag, + + utils.BootnodesFlag, + utils.DataDirFlag, + utils.ListenPortFlag, + utils.LogFileFlag, + utils.LogLevelFlag, + utils.MaxPeersFlag, + utils.MinerThreadsFlag, + utils.NATFlag, + utils.NodeKeyFileFlag, + utils.RPCListenAddrFlag, + utils.RPCPortFlag, } - - gui := NewWindow(ethereum, config, KeyRing, LogLevel) - - utils.RegisterInterrupt(func(os.Signal) { - gui.Stop() - }) - go utils.StartEthereum(ethereum) - - fmt.Println("ETH stack took", time.Since(tstart)) - - // gui blocks the main thread - gui.Start(AssetPath) - - return nil } func main() { @@ -97,15 +73,16 @@ func main() { // This is a bit of a cheat, but ey! os.Setenv("QTWEBKIT_INSPECTOR_SERVER", "127.0.0.1:99999") - qml.Run(run) - var interrupted = false utils.RegisterInterrupt(func(os.Signal) { interrupted = true }) - utils.HandleInterrupt() + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, "Error: ", err) + } + // we need to run the interrupt callbacks in case gui is closed // this skips if we got here by actual interrupt stopping the GUI if !interrupted { @@ -113,3 +90,23 @@ func main() { } logger.Flush() } + +func run(ctx *cli.Context) { + tstart := time.Now() + + // TODO: show qml popup instead of exiting if initialization fails. + ethereum := utils.GetEthereum(ClientIdentifier, Version, ctx) + utils.StartRPC(ethereum, ctx) + go utils.StartEthereum(ethereum) + fmt.Println("initializing eth stack took", time.Since(tstart)) + + // Open the window + qml.Run(func() error { + webengine.Initialize() + gui := NewWindow(ethereum) + utils.RegisterInterrupt(func(os.Signal) { gui.Stop() }) + // gui blocks the main thread + gui.Start(ctx.GlobalString(assetPathFlag.Name)) + return nil + }) +} -- cgit v1.2.3 From cd51860bf024d949a09b3863c88a278a386227a7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 9 Mar 2015 23:06:04 +0100 Subject: cmd/utils: delete InitConfig, KeyTasks --- cmd/utils/cmd.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) (limited to 'cmd') diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 79ae7888a..271a879c7 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/logger" @@ -96,14 +95,6 @@ func initDataDir(Datadir string) { } } -func InitConfig(vmType int, ConfigFile string, Datadir string, EnvPrefix string) *ethutil.ConfigManager { - initDataDir(Datadir) - cfg := ethutil.ReadConfig(ConfigFile, Datadir, EnvPrefix) - cfg.VmType = vmType - - return cfg -} - func exit(err error) { status := 0 if err != nil { @@ -132,37 +123,6 @@ func StartEthereum(ethereum *eth.Ethereum) { }) } -func KeyTasks(keyManager *crypto.KeyManager, KeyRing string, GenAddr bool, SecretFile string, ExportDir string, NonInteractive bool) { - var err error - switch { - case GenAddr: - if NonInteractive || confirm("This action overwrites your old private key.") { - err = keyManager.Init(KeyRing, 0, true) - } - exit(err) - case len(SecretFile) > 0: - SecretFile = ethutil.ExpandHomePath(SecretFile) - - if NonInteractive || confirm("This action overwrites your old private key.") { - err = keyManager.InitFromSecretsFile(KeyRing, 0, SecretFile) - } - exit(err) - case len(ExportDir) > 0: - err = keyManager.Init(KeyRing, 0, false) - if err == nil { - err = keyManager.Export(ExportDir) - } - exit(err) - default: - // Creates a keypair if none exists - err = keyManager.Init(KeyRing, 0, false) - if err != nil { - exit(err) - } - } - clilogger.Infof("Main address %x\n", keyManager.Address()) -} - func FormatTransactionData(data string) []byte { d := ethutil.StringToByteFunc(data, func(s string) (ret []byte) { slice := regexp.MustCompile("\\n|\\s").Split(s, 1000000000) -- cgit v1.2.3 From 487f68ec4892794cb994cffd95d5bc2bf3052d3e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 00:09:39 +0100 Subject: accounts: add {Timed,}Unlock, remove SignLocked --- cmd/utils/flags.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ee7ea4c79..cde5fa024 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -8,7 +8,6 @@ import ( "os" "path" "runtime" - "time" "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/accounts" @@ -199,7 +198,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.D func GetAccountManager(ctx *cli.Context) *accounts.Manager { dataDir := ctx.GlobalString(DataDirFlag.Name) ks := crypto.NewKeyStorePassphrase(path.Join(dataDir, "keys")) - return accounts.NewManager(ks, 300*time.Second) + return accounts.NewManager(ks) } func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { -- cgit v1.2.3 From d1e04f7388ec29b009d646e20570393a5851f54d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 00:24:11 +0100 Subject: cmd/ethereum: allow multiple js files --- cmd/ethereum/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1ae8e46a2..d0edef81d 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -159,13 +159,13 @@ func runjs(ctx *cli.Context) { 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.") + for _, file := range ctx.Args() { + execJsFile(eth, file) + } } + eth.Stop() + eth.WaitForShutdown() } func startEth(ctx *cli.Context, eth *eth.Ethereum) { -- cgit v1.2.3 From 9f0e3bd286472f85ab2457fc19cd48cdf12df110 Mon Sep 17 00:00:00 2001 From: Felix Lange 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(-) (limited to 'cmd') 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 From ae38871a54b1ccb51346ce5d99beb863b7febe66 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:33:59 +0100 Subject: cmd/ethereum: remove "prompter" in identifiers --- cmd/ethereum/js.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index 96e292733..de73e83a2 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -41,14 +41,14 @@ type prompter interface { PasswordPrompt(p string) (string, error) } -type dumbPrompter struct{ r *bufio.Reader } +type dumbterm struct{ r *bufio.Reader } -func (r dumbPrompter) Prompt(p string) (string, error) { +func (r dumbterm) Prompt(p string) (string, error) { fmt.Print(p) return r.r.ReadString('\n') } -func (r dumbPrompter) PasswordPrompt(p string) (string, error) { +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') @@ -56,13 +56,14 @@ func (r dumbPrompter) PasswordPrompt(p string) (string, error) { return input, err } -func (r dumbPrompter) AppendHistory(string) {} +func (r dumbterm) AppendHistory(string) {} type jsre struct { re *javascript.JSRE ethereum *eth.Ethereum xeth *xeth.XEth ps1 string + prompter } @@ -73,7 +74,7 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { js.initStdFuncs() if !liner.TerminalSupported() { - js.prompter = dumbPrompter{bufio.NewReader(os.Stdin)} + js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() lr.SetCtrlCAborts(true) @@ -87,13 +88,13 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool { p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx) - answer, _ := self.prompter.Prompt(p) + 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.prompter.PasswordPrompt("Passphrase: ") + pass, err := self.PasswordPrompt("Passphrase: ") if err != nil { return false } @@ -124,7 +125,7 @@ func (self *jsre) exec(filename string) error { func (self *jsre) interactive() { for { - input, err := self.prompter.Prompt(self.ps1) + input, err := self.Prompt(self.ps1) if err != nil { return } @@ -138,7 +139,7 @@ func (self *jsre) interactive() { return } hist := str[:len(str)-1] - self.prompter.AppendHistory(hist) + self.AppendHistory(hist) self.parseInput(str) str = "" } -- cgit v1.2.3 From 80985f97da8174576ee227909035a364af2fd6c9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 02:35:46 +0100 Subject: cmd/evm, core, ethdb, state, tests/helper: remove ReadConfig calls --- cmd/evm/main.go | 2 -- 1 file changed, 2 deletions(-) (limited to 'cmd') diff --git a/cmd/evm/main.go b/cmd/evm/main.go index d6a93460e..960558bb4 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -59,8 +59,6 @@ func main() { logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel))) - ethutil.ReadConfig("/tmp/evmtest", "/tmp/evm", "") - db, _ := ethdb.NewMemDatabase() statedb := state.New(nil, db) sender := statedb.NewStateObject([]byte("sender")) -- cgit v1.2.3 From 0395c174ca5221dd936c19143ced454dac56d54a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 14:31:54 +0100 Subject: cmd/ethereum: fix line editing setup and history --- cmd/ethereum/js.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go index de73e83a2..5432fb9b1 100644 --- a/cmd/ethereum/js.go +++ b/cmd/ethereum/js.go @@ -63,6 +63,7 @@ type jsre struct { ethereum *eth.Ethereum xeth *xeth.XEth ps1 string + atexit func() prompter } @@ -77,11 +78,13 @@ func newJSRE(ethereum *eth.Ethereum) *jsre { js.prompter = dumbterm{bufio.NewReader(os.Stdin)} } else { lr := liner.NewLiner() - lr.SetCtrlCAborts(true) - 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) }) + lr.SetCtrlCAborts(true) js.prompter = lr + js.atexit = func() { + js.withHistory(func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) }) + lr.Close() + } } return js } @@ -100,7 +103,6 @@ func (self *jsre) UnlockAccount(addr []byte) bool { } // 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.") @@ -127,7 +129,7 @@ func (self *jsre) interactive() { for { input, err := self.Prompt(self.ps1) if err != nil { - return + break } if input == "" { continue @@ -136,7 +138,7 @@ func (self *jsre) interactive() { self.setIndent() if indentCount <= 0 { if input == "exit" { - return + break } hist := str[:len(str)-1] self.AppendHistory(hist) @@ -144,6 +146,9 @@ func (self *jsre) interactive() { str = "" } } + if self.atexit != nil { + self.atexit() + } } func (self *jsre) withHistory(op func(*os.File)) { -- cgit v1.2.3 From c3f94a4341056d36ec9f42dd80702ec7ec6986af Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:42:25 +0100 Subject: cmd/utils: remove extra space in fatal error message --- cmd/utils/cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index a802a08da..c5568948d 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -98,7 +98,7 @@ func initDataDir(Datadir string) { func exit(err error) { status := 0 if err != nil { - fmt.Fprintln(os.Stderr, "Fatal: ", err) + fmt.Fprintln(os.Stderr, "Fatal:", err) status = 1 } logger.Flush() -- cgit v1.2.3 From 0bb7377ebee69c3467c21d355dd24945d0becad5 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 15:44:05 +0100 Subject: cmd/ethereum: show more helpful message if no accounts exist --- cmd/ethereum/main.go | 19 +++++++++++++++++-- cmd/mist/main.go | 5 ++++- cmd/utils/flags.go | 8 ++------ 3 files changed, 23 insertions(+), 9 deletions(-) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 1703c02bb..8beba471a 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -29,6 +29,7 @@ import ( "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" @@ -148,14 +149,28 @@ 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 { diff --git a/cmd/mist/main.go b/cmd/mist/main.go index c27f1dba9..9a773e33a 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -95,7 +95,10 @@ func run(ctx *cli.Context) { tstart := time.Now() // TODO: show qml popup instead of exiting if initialization fails. - ethereum := utils.GetEthereum(ClientIdentifier, Version, ctx) + ethereum, err := utils.GetEthereum(ClientIdentifier, Version, ctx) + if err != nil { + utils.Fatalf("%v", err) + } utils.StartRPC(ethereum, ctx) go utils.StartEthereum(ethereum) fmt.Println("initializing eth stack took", time.Since(tstart)) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cde5fa024..97d312dd4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -157,8 +157,8 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { return key } -func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { - ethereum, err := eth.New(ð.Config{ +func GetEthereum(clientID, version string, ctx *cli.Context) (*eth.Ethereum, error) { + return eth.New(ð.Config{ Name: p2p.MakeName(clientID, version), DataDir: ctx.GlobalString(DataDirFlag.Name), LogFile: ctx.GlobalString(LogFileFlag.Name), @@ -175,10 +175,6 @@ func GetEthereum(clientID, version string, ctx *cli.Context) *eth.Ethereum { Dial: true, BootNodes: ctx.GlobalString(BootnodesFlag.Name), }) - if err != nil { - exit(err) - } - return ethereum } func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.Database) { -- cgit v1.2.3 From 221fae411278825dcaa0ec4058f065752046869b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 16:44:22 +0100 Subject: cmd/ethereum: show some help before prompting for encryption passphrase --- cmd/ethereum/main.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'cmd') diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 8beba471a..ed17fcc1f 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -186,6 +186,7 @@ Please run 'ethereum account new' to create a new account.`) func startEth(ctx *cli.Context, eth *eth.Ethereum) { utils.StartEthereum(eth) + // Start auxiliary services if enabled. if ctx.GlobalBool(utils.RPCEnabledFlag.Name) { utils.StartRPC(eth, ctx) } @@ -207,6 +208,8 @@ func accountList(ctx *cli.Context) { 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) -- cgit v1.2.3 From 972e2c1e31067a9bab77228c19348b66964ce643 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 10 Mar 2015 16:44:48 +0100 Subject: cmd/utils: improve CLI help templates Help for a specific command now shows available subcommands. --- cmd/utils/flags.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'cmd') diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 97d312dd4..e945a32ee 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -24,11 +24,38 @@ import ( "github.com/ethereum/go-ethereum/xeth" ) +func init() { + cli.AppHelpTemplate = `{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] + +VERSION: + {{.Version}} + +COMMANDS: + {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` + + cli.CommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] +{{if .Description}}{{.Description}} +{{end}}{{if .Subcommands}} +SUBCOMMANDS: + {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` +} + // NewApp creates an app with sane defaults. func NewApp(version, usage string) *cli.App { app := cli.NewApp() app.Name = path.Base(os.Args[0]) app.Author = "" + app.Authors = nil app.Email = "" app.Version = version app.Usage = usage -- cgit v1.2.3