Merge pull request #2284 from fjl/accounts-addr-cache

accounts: cache key addresses
This commit is contained in:
Péter Szilágyi
2016-04-12 17:51:09 +03:00
96 changed files with 7364 additions and 1231 deletions

323
cmd/geth/accountcmd.go Normal file
View File

@ -0,0 +1,323 @@
// Copyright 2016 The go-ethereum Authors
// 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 <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"io/ioutil"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
var (
walletCommand = cli.Command{
Name: "wallet",
Usage: "ethereum presale wallet",
Subcommands: []cli.Command{
{
Action: importWallet,
Name: "import",
Usage: "import ethereum presale wallet",
},
},
Description: `
get wallet import /path/to/my/presale.wallet
will prompt for your password and imports your ether presale account.
It can be used non-interactively with the --password option taking a
passwordfile as argument containing the wallet password in plaintext.
`}
accountCommand = cli.Command{
Action: accountList,
Name: "account",
Usage: "manage accounts",
Description: `
Manage accounts lets you create new accounts, list all existing accounts,
import a private key into a new account.
' help' shows a list of subcommands or help for one subcommand.
It supports interactive mode, when you are prompted for password as well as
non-interactive mode where passwords are supplied via a given password file.
Non-interactive mode is only meant for scripted use on test networks or known
safe environments.
Make sure you remember the password you gave when creating a new account (with
either new or import). Without it you are not able to unlock your account.
Note that exporting your key in unencrypted format is NOT supported.
Keys are stored under <DATADIR>/keys.
It is safe to transfer the entire directory or the individual keys therein
between ethereum nodes by simply copying.
Make sure you backup your keys regularly.
In order to use your account to send transactions, you need to unlock them using
the '--unlock' option. The argument is a space separated list of addresses or
indexes. If used non-interactively with a passwordfile, the file should contain
the respective passwords one per line. If you unlock n accounts and the password
file contains less than n entries, then the last password is meant to apply to
all remaining accounts.
And finally. DO NOT FORGET YOUR PASSWORD.
`,
Subcommands: []cli.Command{
{
Action: accountList,
Name: "list",
Usage: "print account addresses",
},
{
Action: accountCreate,
Name: "new",
Usage: "create a new account",
Description: `
ethereum account new
Creates a new account. Prints the address.
The account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the --password flag:
ethereum --password <passwordfile> account new
Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way.
`,
},
{
Action: accountUpdate,
Name: "update",
Usage: "update an existing account",
Description: `
ethereum account update <address>
Update an existing account.
The account is saved in the newest version in encrypted format, you are prompted
for a passphrase to unlock the account and another to save the updated file.
This same command can therefore be used to migrate an account of a deprecated
format to the newest format or change the password for an account.
For non-interactive use the passphrase can be specified with the --password flag:
ethereum --password <passwordfile> account update <address>
Since only one password can be given, only format update can be performed,
changing your password is only possible interactively.
`,
},
{
Action: accountImport,
Name: "import",
Usage: "import a private key into a new account",
Description: `
ethereum account import <keyfile>
Imports an unencrypted private key from <keyfile> and creates a new account.
Prints the address.
The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
The account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the -password flag:
ethereum --password <passwordfile> account import <keyfile>
Note:
As you can directly copy your encrypted accounts to another ethereum instance,
this import mechanism is not needed when you transfer an account between
nodes.
`,
},
},
}
)
func accountList(ctx *cli.Context) {
accman := utils.MakeAccountManager(ctx)
for i, acct := range accman.Accounts() {
fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
}
}
// tries unlocking the specified account a few times.
func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) {
account, err := utils.MakeAddress(accman, address)
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
}
for trials := 0; trials < 3; trials++ {
prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
password := getPassPhrase(prompt, false, i, passwords)
err = accman.Unlock(account, password)
if err == nil {
glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
return account, password
}
if err, ok := err.(*accounts.AmbiguousAddrError); ok {
glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
return ambiguousAddrRecovery(accman, err, password), password
}
if err != accounts.ErrDecrypt {
// No need to prompt again if the error is not decryption-related.
break
}
}
// All trials expended to unlock account, bail out
utils.Fatalf("Failed to unlock account %s (%v)", address, err)
return accounts.Account{}, ""
}
// getPassPhrase retrieves the passwor associated with an account, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// Otherwise prompt the user for the password
if prompt != "" {
fmt.Println(prompt)
}
password, err := utils.Stdin.PasswordPrompt("Passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := utils.Stdin.PasswordPrompt("Repeat passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}
func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account {
fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
for _, a := range err.Matches {
fmt.Println(" ", a.File)
}
fmt.Println("Testing your passphrase against all of them...")
var match *accounts.Account
for _, a := range err.Matches {
if err := am.Unlock(a, auth); err == nil {
match = &a
break
}
}
if match == nil {
utils.Fatalf("None of the listed files could be unlocked.")
}
fmt.Printf("Your passphrase unlocked %s\n", match.File)
fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:")
for _, a := range err.Matches {
if a != *match {
fmt.Println(" ", a.File)
}
}
return *match
}
// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) {
accman := utils.MakeAccountManager(ctx)
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := accman.NewAccount(password)
if err != nil {
utils.Fatalf("Failed to create account: %v", err)
}
fmt.Printf("Address: {%x}\n", account.Address)
}
// accountUpdate transitions an account from a previous format to the current
// one, also providing the possibility to change the pass-phrase.
func accountUpdate(ctx *cli.Context) {
if len(ctx.Args()) == 0 {
utils.Fatalf("No accounts specified to update")
}
accman := utils.MakeAccountManager(ctx)
account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
if err := accman.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err)
}
}
func importWallet(ctx *cli.Context) {
keyfile := ctx.Args().First()
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
keyJson, err := ioutil.ReadFile(keyfile)
if err != nil {
utils.Fatalf("Could not read wallet file: %v", err)
}
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
if err != nil {
utils.Fatalf("%v", err)
}
fmt.Printf("Address: {%x}\n", acct.Address)
}
func accountImport(ctx *cli.Context) {
keyfile := ctx.Args().First()
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
key, err := crypto.LoadECDSA(keyfile)
if err != nil {
utils.Fatalf("keyfile must be given as argument")
}
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
acct, err := accman.ImportECDSA(key, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
fmt.Printf("Address: {%x}\n", acct.Address)
}

292
cmd/geth/accountcmd_test.go Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2016 The go-ethereum Authors
// 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 <http://www.gnu.org/licenses/>.
package main
import (
"io/ioutil"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/cespare/cp"
)
// These tests are 'smoke tests' for the account related
// subcommands and flags.
//
// For most tests, the test files from package accounts
// are copied into a temporary keystore directory.
func tmpDatadirWithKeystore(t *testing.T) string {
datadir := tmpdir(t)
keystore := filepath.Join(datadir, "keystore")
source := filepath.Join("..", "..", "accounts", "testdata", "keystore")
if err := cp.CopyAll(keystore, source); err != nil {
t.Fatal(err)
}
return datadir
}
func TestAccountListEmpty(t *testing.T) {
geth := runGeth(t, "account")
geth.expectExit()
}
func TestAccountList(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t, "--datadir", datadir, "account")
defer geth.expectExit()
if runtime.GOOS == "windows" {
geth.expect(`
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} {{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} {{.Datadir}}\keystore\aaa
Account #2: {289d485d9771714cce91d3393d764e1311907acc} {{.Datadir}}\keystore\zzz
`)
} else {
geth.expect(`
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} {{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} {{.Datadir}}/keystore/aaa
Account #2: {289d485d9771714cce91d3393d764e1311907acc} {{.Datadir}}/keystore/zzz
`)
}
}
func TestAccountNew(t *testing.T) {
geth := runGeth(t, "--lightkdf", "account", "new")
defer geth.expectExit()
geth.expect(`
Your new account is locked with a password. Please give a password. Do not forget this password.
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Repeat passphrase: {{.InputLine "foobar"}}
`)
geth.expectRegexp(`Address: \{[0-9a-f]{40}\}\n`)
}
func TestAccountNewBadRepeat(t *testing.T) {
geth := runGeth(t, "--lightkdf", "account", "new")
defer geth.expectExit()
geth.expect(`
Your new account is locked with a password. Please give a password. Do not forget this password.
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "something"}}
Repeat passphrase: {{.InputLine "something else"}}
Fatal: Passphrases do not match
`)
}
func TestAccountUpdate(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--lightkdf",
"account", "update", "f466859ead1932d743d622cb74fc058882e8648a")
defer geth.expectExit()
geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Please give a new password. Do not forget this password.
Passphrase: {{.InputLine "foobar2"}}
Repeat passphrase: {{.InputLine "foobar2"}}
`)
}
func TestWalletImport(t *testing.T) {
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json")
defer geth.expectExit()
geth.expect(`
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foo"}}
Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
`)
files, err := ioutil.ReadDir(filepath.Join(geth.Datadir, "keystore"))
if len(files) != 1 {
t.Errorf("expected one key file in keystore directory, found %d files (error: %v)", len(files), err)
}
}
func TestWalletImportBadPassword(t *testing.T) {
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json")
defer geth.expectExit()
geth.expect(`
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong"}}
Fatal: could not decrypt key with given passphrase
`)
}
func TestUnlockFlag(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
"js", "testdata/empty.js")
geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
`)
geth.expectExit()
wantMessages := []string{
"Unlocked account f466859ead1932d743d622cb74fc058882e8648a",
}
for _, m := range wantMessages {
if strings.Index(geth.stderrText(), m) == -1 {
t.Errorf("stderr text does not contain %q", m)
}
}
}
func TestUnlockFlagWrongPassword(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
defer geth.expectExit()
geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong1"}}
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 2/3
Passphrase: {{.InputLine "wrong2"}}
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3
Passphrase: {{.InputLine "wrong3"}}
Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could not decrypt key with given passphrase)
`)
}
// https://github.com/ethereum/go-ethereum/issues/1785
func TestUnlockFlagMultiIndex(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "0,2",
"js", "testdata/empty.js")
geth.expect(`
Unlocking account 0 | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Unlocking account 2 | Attempt 1/3
Passphrase: {{.InputLine "foobar"}}
`)
geth.expectExit()
wantMessages := []string{
"Unlocked account 7ef5a6135f1fd6a02593eedc869c6d41d934aef8",
"Unlocked account 289d485d9771714cce91d3393d764e1311907acc",
}
for _, m := range wantMessages {
if strings.Index(geth.stderrText(), m) == -1 {
t.Errorf("stderr text does not contain %q", m)
}
}
}
func TestUnlockFlagPasswordFile(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--password", "testdata/passwords.txt", "--unlock", "0,2",
"js", "testdata/empty.js")
geth.expectExit()
wantMessages := []string{
"Unlocked account 7ef5a6135f1fd6a02593eedc869c6d41d934aef8",
"Unlocked account 289d485d9771714cce91d3393d764e1311907acc",
}
for _, m := range wantMessages {
if strings.Index(geth.stderrText(), m) == -1 {
t.Errorf("stderr text does not contain %q", m)
}
}
}
func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
datadir := tmpDatadirWithKeystore(t)
geth := runGeth(t,
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
defer geth.expectExit()
geth.expect(`
Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
`)
}
func TestUnlockFlagAmbiguous(t *testing.T) {
store := filepath.Join("..", "..", "accounts", "testdata", "dupes")
geth := runGeth(t,
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
"js", "testdata/empty.js")
defer geth.expectExit()
// Helper for the expect template, returns absolute keystore path.
geth.setTemplateFunc("keypath", func(file string) string {
abs, _ := filepath.Abs(filepath.Join(store, file))
return abs
})
geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "foobar"}}
Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a:
{{keypath "1"}}
{{keypath "2"}}
Testing your passphrase against all of them...
Your passphrase unlocked {{keypath "1"}}
In order to avoid this warning, you need to remove the following duplicate key files:
{{keypath "2"}}
`)
geth.expectExit()
wantMessages := []string{
"Unlocked account f466859ead1932d743d622cb74fc058882e8648a",
}
for _, m := range wantMessages {
if strings.Index(geth.stderrText(), m) == -1 {
t.Errorf("stderr text does not contain %q", m)
}
}
}
func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
store := filepath.Join("..", "..", "accounts", "testdata", "dupes")
geth := runGeth(t,
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
defer geth.expectExit()
// Helper for the expect template, returns absolute keystore path.
geth.setTemplateFunc("keypath", func(file string) string {
abs, _ := filepath.Abs(filepath.Join(store, file))
return abs
})
geth.expect(`
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
!! Unsupported terminal, password will be echoed.
Passphrase: {{.InputLine "wrong"}}
Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a:
{{keypath "1"}}
{{keypath "2"}}
Testing your passphrase against all of them...
Fatal: None of the listed files could be unlocked.
`)
geth.expectExit()
}

View File

@ -116,7 +116,7 @@ func exportChain(ctx *cli.Context) {
}
func removeDB(ctx *cli.Context) {
confirm, err := utils.PromptConfirm("Remove local database?")
confirm, err := utils.Stdin.ConfirmPrompt("Remove local database?")
if err != nil {
utils.Fatalf("%v", err)
}

View File

@ -17,7 +17,6 @@
package main
import (
"bufio"
"fmt"
"math/big"
"os"
@ -28,6 +27,7 @@ import (
"strings"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/registrar"
@ -46,30 +46,6 @@ var (
exit = regexp.MustCompile("^\\s*exit\\s*;*\\s*$")
)
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)
line, err := r.r.ReadString('\n')
return strings.TrimSuffix(line, "\n"), err
}
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 *re.JSRE
stack *node.Node
@ -78,7 +54,6 @@ type jsre struct {
atexit func()
corsDomain string
client rpc.Client
prompter
}
func makeCompleter(re *jsre) liner.WordCompleter {
@ -106,27 +81,11 @@ func newLightweightJSRE(docRoot string, client rpc.Client, datadir string, inter
js := &jsre{ps1: "> "}
js.wait = make(chan *big.Int)
js.client = client
js.re = re.New(docRoot)
if err := js.apiBindings(); err != nil {
utils.Fatalf("Unable to initialize console - %v", err)
}
if !liner.TerminalSupported() || !interactive {
js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else {
lr := liner.NewLiner()
js.withHistory(datadir, func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
lr.SetWordCompleter(makeCompleter(js))
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr
js.atexit = func() {
js.withHistory(datadir, func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
lr.Close()
close(js.wait)
}
}
js.setupInput(datadir)
return js
}
@ -136,30 +95,29 @@ func newJSRE(stack *node.Node, docRoot, corsDomain string, client rpc.Client, in
js.corsDomain = corsDomain
js.wait = make(chan *big.Int)
js.client = client
js.re = re.New(docRoot)
if err := js.apiBindings(); err != nil {
utils.Fatalf("Unable to connect - %v", err)
}
if !liner.TerminalSupported() || !interactive {
js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
} else {
lr := liner.NewLiner()
js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
lr.SetWordCompleter(makeCompleter(js))
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr
js.atexit = func() {
js.withHistory(stack.DataDir(), func(hist *os.File) { hist.Truncate(0); lr.WriteHistory(hist) })
lr.Close()
close(js.wait)
}
}
js.setupInput(stack.DataDir())
return js
}
func (self *jsre) setupInput(datadir string) {
self.withHistory(datadir, func(hist *os.File) { utils.Stdin.ReadHistory(hist) })
utils.Stdin.SetCtrlCAborts(true)
utils.Stdin.SetWordCompleter(makeCompleter(self))
utils.Stdin.SetTabCompletionStyle(liner.TabPrints)
self.atexit = func() {
self.withHistory(datadir, func(hist *os.File) {
hist.Truncate(0)
utils.Stdin.WriteHistory(hist)
})
utils.Stdin.Close()
close(self.wait)
}
}
func (self *jsre) batch(statement string) {
err := self.re.EvalAndPrettyPrint(statement)
@ -290,7 +248,7 @@ func (js *jsre) apiBindings() error {
}
func (self *jsre) AskPassword() (string, bool) {
pass, err := self.PasswordPrompt("Passphrase: ")
pass, err := utils.Stdin.PasswordPrompt("Passphrase: ")
if err != nil {
return "", false
}
@ -315,7 +273,7 @@ func (self *jsre) ConfirmTransaction(tx string) bool {
func (self *jsre) UnlockAccount(addr []byte) bool {
fmt.Printf("Please unlock account %x.\n", addr)
pass, err := self.PasswordPrompt("Passphrase: ")
pass, err := utils.Stdin.PasswordPrompt("Passphrase: ")
if err != nil {
return false
}
@ -324,7 +282,8 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
if err := self.stack.Service(&ethereum); err != nil {
return false
}
if err := ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
a := accounts.Account{Address: common.BytesToAddress(addr)}
if err := ethereum.AccountManager().Unlock(a, pass); err != nil {
return false
} else {
fmt.Println("Account is now unlocked for this session.")
@ -365,7 +324,7 @@ func (self *jsre) interactive() {
go func() {
defer close(inputln)
for {
line, err := self.Prompt(<-prompt)
line, err := utils.Stdin.Prompt(<-prompt)
if err != nil {
if err == liner.ErrPromptAborted { // ctrl-C
self.resetPrompt()
@ -404,7 +363,7 @@ func (self *jsre) interactive() {
self.setIndent()
if indentCount <= 0 {
if mustLogInHistory(str) {
self.AppendHistory(str[:len(str)-1])
utils.Stdin.AppendHistory(str[:len(str)-1])
}
self.parseInput(str)
str = ""

View File

@ -42,18 +42,17 @@ import (
const (
testSolcPath = ""
solcVersion = "0.9.23"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000"
// of empty string
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
)
var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey, _ = crypto.HexToECDSA("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")
testAccount, _ = crypto.HexToECDSA("e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674")
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
)
type testjethre struct {
@ -62,17 +61,6 @@ type testjethre struct {
client *httpclient.HTTPClient
}
func (self *testjethre) UnlockAccount(acc []byte) bool {
var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil {
panic("unable to unlock")
}
return true
}
// Temporary disabled while natspec hasn't been migrated
//func (self *testjethre) ConfirmTransaction(tx string) bool {
// var ethereum *eth.Ethereum
@ -94,17 +82,14 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
t.Fatal(err)
}
// Create a networkless protocol stack
stack, err := node.New(&node.Config{PrivateKey: testNodeKey, Name: "test", NoDiscovery: true})
stack, err := node.New(&node.Config{DataDir: tmp, PrivateKey: testNodeKey, Name: "test", NoDiscovery: true})
if err != nil {
t.Fatalf("failed to create node: %v", err)
}
// Initialize and register the Ethereum protocol
keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore"))
accman := accounts.NewManager(keystore)
accman := accounts.NewPlaintextManager(filepath.Join(tmp, "keystore"))
db, _ := ethdb.NewMemDatabase()
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)})
ethConf := &eth.Config{
ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)},
TestGenesisState: db,
@ -122,15 +107,11 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
t.Fatalf("failed to register ethereum protocol: %v", err)
}
// Initialize all the keys for testing
keyb, err := crypto.HexToECDSA(testKey)
a, err := accman.ImportECDSA(testAccount, "")
if err != nil {
t.Fatal(err)
}
key := crypto.NewKeyFromECDSA(keyb)
if err := keystore.StoreKey(key, ""); err != nil {
t.Fatal(err)
}
if err := accman.Unlock(key.Address, ""); err != nil {
if err := accman.Unlock(a, ""); err != nil {
t.Fatal(err)
}
// Start the node and assemble the REPL tester

View File

@ -29,7 +29,6 @@ import (
"github.com/codegangsta/cli"
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@ -75,6 +74,8 @@ func init() {
removedbCommand,
dumpCommand,
monitorCommand,
accountCommand,
walletCommand,
{
Action: makedag,
Name: "makedag",
@ -110,144 +111,6 @@ Runs quick benchmark on first GPU found.
The output of this command is supposed to be machine-readable.
`,
},
{
Name: "wallet",
Usage: "ethereum presale wallet",
Subcommands: []cli.Command{
{
Action: importWallet,
Name: "import",
Usage: "import ethereum presale wallet",
},
},
Description: `
get wallet import /path/to/my/presale.wallet
will prompt for your password and imports your ether presale account.
It can be used non-interactively with the --password option taking a
passwordfile as argument containing the wallet password in plaintext.
`},
{
Action: accountList,
Name: "account",
Usage: "manage accounts",
Description: `
Manage accounts lets you create new accounts, list all existing accounts,
import a private key into a new account.
' help' shows a list of subcommands or help for one subcommand.
It supports interactive mode, when you are prompted for password as well as
non-interactive mode where passwords are supplied via a given password file.
Non-interactive mode is only meant for scripted use on test networks or known
safe environments.
Make sure you remember the password you gave when creating a new account (with
either new or import). Without it you are not able to unlock your account.
Note that exporting your key in unencrypted format is NOT supported.
Keys are stored under <DATADIR>/keys.
It is safe to transfer the entire directory or the individual keys therein
between ethereum nodes by simply copying.
Make sure you backup your keys regularly.
In order to use your account to send transactions, you need to unlock them using
the '--unlock' option. The argument is a space separated list of addresses or
indexes. If used non-interactively with a passwordfile, the file should contain
the respective passwords one per line. If you unlock n accounts and the password
file contains less than n entries, then the last password is meant to apply to
all remaining accounts.
And finally. DO NOT FORGET YOUR PASSWORD.
`,
Subcommands: []cli.Command{
{
Action: accountList,
Name: "list",
Usage: "print account addresses",
},
{
Action: accountCreate,
Name: "new",
Usage: "create a new account",
Description: `
ethereum account new
Creates a new account. Prints the address.
The account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the --password flag:
ethereum --password <passwordfile> account new
Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way.
`,
},
{
Action: accountUpdate,
Name: "update",
Usage: "update an existing account",
Description: `
ethereum account update <address>
Update an existing account.
The account is saved in the newest version in encrypted format, you are prompted
for a passphrase to unlock the account and another to save the updated file.
This same command can therefore be used to migrate an account of a deprecated
format to the newest format or change the password for an account.
For non-interactive use the passphrase can be specified with the --password flag:
ethereum --password <passwordfile> account update <address>
Since only one password can be given, only format update can be performed,
changing your password is only possible interactively.
Note that account update has the a side effect that the order of your accounts
changes.
`,
},
{
Action: accountImport,
Name: "import",
Usage: "import a private key into a new account",
Description: `
ethereum account import <keyfile>
Imports an unencrypted private key from <keyfile> and creates a new account.
Prints the address.
The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
The account is saved in encrypted format, you are prompted for a passphrase.
You must remember this passphrase to unlock your account in the future.
For non-interactive use the passphrase can be specified with the -password flag:
ethereum --password <passwordfile> account import <keyfile>
Note:
As you can directly copy your encrypted accounts to another ethereum instance,
this import mechanism is not needed when you transfer an account between
nodes.
`,
},
},
},
{
Action: initGenesis,
Name: "init",
@ -289,6 +152,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
`,
},
}
app.Flags = []cli.Flag{
utils.IdentityFlag,
utils.UnlockedAccountFlag,
@ -373,6 +237,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
app.After = func(ctx *cli.Context) error {
logger.Flush()
debug.Exit()
utils.Stdin.Close() // Resets terminal mode.
return nil
}
}
@ -524,25 +389,6 @@ func execScripts(ctx *cli.Context) {
node.Stop()
}
// tries unlocking the specified account a few times.
func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (common.Address, string) {
account, err := utils.MakeAddress(accman, address)
if err != nil {
utils.Fatalf("Unlock error: %v", err)
}
for trials := 0; trials < 3; trials++ {
prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
password := getPassPhrase(prompt, false, i, passwords)
if err := accman.Unlock(account, password); err == nil {
return account, password
}
}
// All trials expended to unlock account, bail out
utils.Fatalf("Failed to unlock account: %s", address)
return common.Address{}, ""
}
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
@ -572,106 +418,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
}
}
func accountList(ctx *cli.Context) {
accman := utils.MakeAccountManager(ctx)
accts, err := accman.Accounts()
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
}
for i, acct := range accts {
fmt.Printf("Account #%d: %x\n", i, acct)
}
}
// getPassPhrase retrieves the passwor associated with an account, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// Otherwise prompt the user for the password
fmt.Println(prompt)
password, err := utils.PromptPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := utils.PromptPassword("Repeat passphrase: ", false)
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}
// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) {
accman := utils.MakeAccountManager(ctx)
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := accman.NewAccount(password)
if err != nil {
utils.Fatalf("Failed to create account: %v", err)
}
fmt.Printf("Address: %x\n", account)
}
// accountUpdate transitions an account from a previous format to the current
// one, also providing the possibility to change the pass-phrase.
func accountUpdate(ctx *cli.Context) {
if len(ctx.Args()) == 0 {
utils.Fatalf("No accounts specified to update")
}
accman := utils.MakeAccountManager(ctx)
account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
if err := accman.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err)
}
}
func importWallet(ctx *cli.Context) {
keyfile := ctx.Args().First()
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
keyJson, err := ioutil.ReadFile(keyfile)
if err != nil {
utils.Fatalf("Could not read wallet file: %v", err)
}
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
fmt.Printf("Address: %x\n", acct)
}
func accountImport(ctx *cli.Context) {
keyfile := ctx.Args().First()
if len(keyfile) == 0 {
utils.Fatalf("keyfile must be given as argument")
}
accman := utils.MakeAccountManager(ctx)
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
acct, err := accman.Import(keyfile, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
fmt.Printf("Address: %x\n", acct)
}
func makedag(ctx *cli.Context) {
args := ctx.Args()
wrongArgs := func() {

290
cmd/geth/run_test.go Normal file
View File

@ -0,0 +1,290 @@
// Copyright 2016 The go-ethereum Authors
// 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 <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"os/exec"
"regexp"
"sync"
"testing"
"time"
)
func tmpdir(t *testing.T) string {
dir, err := ioutil.TempDir("", "geth-test")
if err != nil {
t.Fatal(err)
}
return dir
}
type testgeth struct {
// For total convenience, all testing methods are available.
*testing.T
// template variables for expect
Datadir string
Executable string
Func template.FuncMap
removeDatadir bool
cmd *exec.Cmd
stdout *bufio.Reader
stdin io.WriteCloser
stderr *testlogger
}
func init() {
// Run the app if we're the child process for runGeth.
if os.Getenv("GETH_TEST_CHILD") != "" {
app.RunAndExitOnError()
os.Exit(0)
}
}
// spawns geth with the given command line args. If the args don't set --datadir, the
// child g gets a temporary data directory.
func runGeth(t *testing.T, args ...string) *testgeth {
tt := &testgeth{T: t, Executable: os.Args[0]}
for i, arg := range args {
if arg == "-datadir" || arg == "--datadir" {
if i < len(args)-1 {
tt.Datadir = args[i+1]
}
break
}
}
if tt.Datadir == "" {
tt.Datadir = tmpdir(t)
tt.removeDatadir = true
args = append([]string{"-datadir", tt.Datadir}, args...)
// Remove the temporary datadir if something fails below.
defer func() {
if t.Failed() {
os.RemoveAll(tt.Datadir)
}
}()
}
// Boot "geth". This actually runs the test binary but the init function
// will prevent any tests from running.
tt.stderr = &testlogger{t: t}
tt.cmd = exec.Command(os.Args[0], args...)
tt.cmd.Env = append(os.Environ(), "GETH_TEST_CHILD=1")
tt.cmd.Stderr = tt.stderr
stdout, err := tt.cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
tt.stdout = bufio.NewReader(stdout)
if tt.stdin, err = tt.cmd.StdinPipe(); err != nil {
t.Fatal(err)
}
if err := tt.cmd.Start(); err != nil {
t.Fatal(err)
}
return tt
}
// InputLine writes the given text to the childs stdin.
// This method can also be called from an expect template, e.g.:
//
// geth.expect(`Passphrase: {{.InputLine "password"}}`)
func (tt *testgeth) InputLine(s string) string {
io.WriteString(tt.stdin, s+"\n")
return ""
}
func (tt *testgeth) setTemplateFunc(name string, fn interface{}) {
if tt.Func == nil {
tt.Func = make(map[string]interface{})
}
tt.Func[name] = fn
}
// expect runs its argument as a template, then expects the
// child process to output the result of the template within 5s.
//
// If the template starts with a newline, the newline is removed
// before matching.
func (tt *testgeth) expect(tplsource string) {
// Generate the expected output by running the template.
tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource))
wantbuf := new(bytes.Buffer)
if err := tpl.Execute(wantbuf, tt); err != nil {
panic(err)
}
// Trim exactly one newline at the beginning. This makes tests look
// much nicer because all expect strings are at column 0.
want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n"))
if err := tt.matchExactOutput(want); err != nil {
tt.Fatal(err)
}
tt.Logf("Matched stdout text:\n%s", want)
}
func (tt *testgeth) matchExactOutput(want []byte) error {
buf := make([]byte, len(want))
n := 0
tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) })
buf = buf[:n]
if n < len(want) || !bytes.Equal(buf, want) {
// Grab any additional buffered output in case of mismatch
// because it might help with debugging.
buf = append(buf, make([]byte, tt.stdout.Buffered())...)
tt.stdout.Read(buf[n:])
// Find the mismatch position.
for i := 0; i < n; i++ {
if want[i] != buf[i] {
return fmt.Errorf("Output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s",
buf[:i], buf[i:n], want)
}
}
if n < len(want) {
return fmt.Errorf("Not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s",
buf, want[:n], want[n:])
}
}
return nil
}
// expectRegexp expects the child process to output text matching the
// given regular expression within 5s.
//
// Note that an arbitrary amount of output may be consumed by the
// regular expression. This usually means that expect cannot be used
// after expectRegexp.
func (tt *testgeth) expectRegexp(resource string) (*regexp.Regexp, []string) {
var (
re = regexp.MustCompile(resource)
rtee = &runeTee{in: tt.stdout}
matches []int
)
tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) })
output := rtee.buf.Bytes()
if matches == nil {
tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s",
output, resource)
return re, nil
}
tt.Logf("Matched stdout text:\n%s", output)
var submatch []string
for i := 0; i < len(matches); i += 2 {
submatch = append(submatch, string(output[i:i+1]))
}
return re, submatch
}
// expectExit expects the child process to exit within 5s without
// printing any additional text on stdout.
func (tt *testgeth) expectExit() {
var output []byte
tt.withKillTimeout(func() {
output, _ = ioutil.ReadAll(tt.stdout)
})
tt.cmd.Wait()
if tt.removeDatadir {
os.RemoveAll(tt.Datadir)
}
if len(output) > 0 {
tt.Errorf("Unmatched stdout text:\n%s", output)
}
}
func (tt *testgeth) interrupt() {
tt.cmd.Process.Signal(os.Interrupt)
}
// stderrText returns any stderr output written so far.
// The returned text holds all log lines after expectExit has
// returned.
func (tt *testgeth) stderrText() string {
tt.stderr.mu.Lock()
defer tt.stderr.mu.Unlock()
return tt.stderr.buf.String()
}
func (tt *testgeth) withKillTimeout(fn func()) {
timeout := time.AfterFunc(5*time.Second, func() {
tt.Log("killing the child process (timeout)")
tt.cmd.Process.Kill()
if tt.removeDatadir {
os.RemoveAll(tt.Datadir)
}
})
defer timeout.Stop()
fn()
}
// testlogger logs all written lines via t.Log and also
// collects them for later inspection.
type testlogger struct {
t *testing.T
mu sync.Mutex
buf bytes.Buffer
}
func (tl *testlogger) Write(b []byte) (n int, err error) {
lines := bytes.Split(b, []byte("\n"))
for _, line := range lines {
if len(line) > 0 {
tl.t.Logf("(stderr) %s", line)
}
}
tl.mu.Lock()
tl.buf.Write(b)
tl.mu.Unlock()
return len(b), err
}
// runeTee collects text read through it into buf.
type runeTee struct {
in interface {
io.Reader
io.ByteReader
io.RuneReader
}
buf bytes.Buffer
}
func (rtee *runeTee) Read(b []byte) (n int, err error) {
n, err = rtee.in.Read(b)
rtee.buf.Write(b[:n])
return n, err
}
func (rtee *runeTee) ReadRune() (r rune, size int, err error) {
r, size, err = rtee.in.ReadRune()
if err == nil {
rtee.buf.WriteRune(r)
}
return r, size, err
}
func (rtee *runeTee) ReadByte() (b byte, err error) {
b, err = rtee.in.ReadByte()
if err == nil {
rtee.buf.WriteByte(b)
}
return b, err
}

1
cmd/geth/testdata/empty.js vendored Normal file
View File

@ -0,0 +1 @@

6
cmd/geth/testdata/guswallet.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"encseed": "26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba",
"ethaddr": "d4584b5f6229b7be90727b0fc8c6b91bb427821f",
"email": "gustav.simonsson@gmail.com",
"btcaddr": "1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx"
}

3
cmd/geth/testdata/passwords.txt vendored Normal file
View File

@ -0,0 +1,3 @@
foobar
foobar
foobar

3
cmd/geth/testdata/wrong-passwords.txt vendored Normal file
View File

@ -0,0 +1,3 @@
wrong
wrong
wrong

View File

@ -108,24 +108,23 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node
return nil, err
}
// Create the keystore and inject an unlocked account if requested
keystore := crypto.NewKeyStorePassphrase(keydir, crypto.StandardScryptN, crypto.StandardScryptP)
accman := accounts.NewManager(keystore)
accman := accounts.NewPlaintextManager(keydir)
if len(privkey) > 0 {
key, err := crypto.HexToECDSA(privkey)
if err != nil {
return nil, err
}
if err := keystore.StoreKey(crypto.NewKeyFromECDSA(key), ""); err != nil {
a, err := accman.ImportECDSA(key, "")
if err != nil {
return nil, err
}
if err := accman.Unlock(crypto.NewKeyFromECDSA(key).Address, ""); err != nil {
if err := accman.Unlock(a, ""); err != nil {
return nil, err
}
}
// Initialize and register the Ethereum protocol
db, _ := ethdb.NewMemDatabase()
if _, err := test.InsertPreState(db, accman); err != nil {
if _, err := test.InsertPreState(db); err != nil {
return nil, err
}
ethConf := &eth.Config{

View File

@ -18,13 +18,11 @@
package utils
import (
"bufio"
"fmt"
"io"
"os"
"os/signal"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@ -34,17 +32,12 @@ import (
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/peterh/liner"
)
const (
importBatchSize = 2500
)
var (
interruptCallbacks = []func(os.Signal){}
)
func openLogFile(Datadir string, filename string) *os.File {
path := common.AbsolutePath(Datadir, filename)
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
@ -54,49 +47,6 @@ func openLogFile(Datadir string, filename string) *os.File {
return file
}
func PromptConfirm(prompt string) (bool, error) {
var (
input string
err error
)
prompt = prompt + " [y/N] "
// if liner.TerminalSupported() {
// fmt.Println("term")
// lr := liner.NewLiner()
// defer lr.Close()
// input, err = lr.Prompt(prompt)
// } else {
fmt.Print(prompt)
input, err = bufio.NewReader(os.Stdin).ReadString('\n')
fmt.Println()
// }
if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" {
return true, nil
} else {
return false, nil
}
return false, err
}
func PromptPassword(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')
input = strings.TrimRight(input, "\r\n")
fmt.Println()
return input, err
}
// Fatalf formats a message to standard error and exits the program.
// The message is also printed to standard output if standard error
// is redirected to a different file.

View File

@ -551,45 +551,36 @@ func MakeDatabaseHandles() int {
// MakeAccountManager creates an account manager from set command line flags.
func MakeAccountManager(ctx *cli.Context) *accounts.Manager {
// Create the keystore crypto primitive, light if requested
scryptN := crypto.StandardScryptN
scryptP := crypto.StandardScryptP
scryptN := accounts.StandardScryptN
scryptP := accounts.StandardScryptP
if ctx.GlobalBool(LightKDFFlag.Name) {
scryptN = crypto.LightScryptN
scryptP = crypto.LightScryptP
scryptN = accounts.LightScryptN
scryptP = accounts.LightScryptP
}
// Assemble an account manager using the configured datadir
var (
datadir = MustMakeDataDir(ctx)
keystoredir = MakeKeyStoreDir(datadir, ctx)
keystore = crypto.NewKeyStorePassphrase(keystoredir, scryptN, scryptP)
)
return accounts.NewManager(keystore)
datadir := MustMakeDataDir(ctx)
keydir := MakeKeyStoreDir(datadir, ctx)
return accounts.NewManager(keydir, scryptN, scryptP)
}
// MakeAddress converts an account specified directly as a hex encoded string or
// a key index in the key store to an internal account representation.
func MakeAddress(accman *accounts.Manager, account string) (a common.Address, err error) {
func MakeAddress(accman *accounts.Manager, account string) (accounts.Account, error) {
// If the specified account is a valid address, return it
if common.IsHexAddress(account) {
return common.HexToAddress(account), nil
return accounts.Account{Address: common.HexToAddress(account)}, nil
}
// Otherwise try to interpret the account as a keystore index
index, err := strconv.Atoi(account)
if err != nil {
return a, fmt.Errorf("invalid account address or index %q", account)
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
}
hex, err := accman.AddressByIndex(index)
if err != nil {
return a, fmt.Errorf("can't get account #%d (%v)", index, err)
}
return common.HexToAddress(hex), nil
return accman.AccountByIndex(index)
}
// MakeEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed.
func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address {
accounts, _ := accman.Accounts()
accounts := accman.Accounts()
if !ctx.GlobalIsSet(EtherbaseFlag.Name) && len(accounts) == 0 {
glog.V(logger.Error).Infoln("WARNING: No etherbase set and no accounts found as default")
return common.Address{}
@ -599,11 +590,11 @@ func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address {
return common.Address{}
}
// If the specified etherbase is a valid address, return it
addr, err := MakeAddress(accman, etherbase)
account, err := MakeAddress(accman, etherbase)
if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
return addr
return account.Address
}
// MakeMinerExtra resolves extradata for the miner from the set command line flags
@ -615,17 +606,22 @@ func MakeMinerExtra(extra []byte, ctx *cli.Context) []byte {
return extra
}
// MakePasswordList loads up a list of password from a file specified by the
// command line flags.
// MakePasswordList reads password lines from the file specified by --password.
func MakePasswordList(ctx *cli.Context) []string {
if path := ctx.GlobalString(PasswordFileFlag.Name); path != "" {
blob, err := ioutil.ReadFile(path)
if err != nil {
Fatalf("Failed to read password file: %v", err)
}
return strings.Split(string(blob), "\n")
path := ctx.GlobalString(PasswordFileFlag.Name)
if path == "" {
return nil
}
return nil
text, err := ioutil.ReadFile(path)
if err != nil {
Fatalf("Failed to read password file: %v", err)
}
lines := strings.Split(string(text), "\n")
// Sanitise DOS line endings.
for i := range lines {
lines[i] = strings.TrimRight(lines[i], "\r")
}
return lines
}
// MakeSystemNode sets up a local node, configures the services to launch and

98
cmd/utils/input.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2016 The go-ethereum Authors
// 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 <http://www.gnu.org/licenses/>.
package utils
import (
"fmt"
"strings"
"github.com/peterh/liner"
)
// Holds the stdin line reader.
// Only this reader may be used for input because it keeps
// an internal buffer.
var Stdin = newUserInputReader()
type userInputReader struct {
*liner.State
warned bool
supported bool
normalMode liner.ModeApplier
rawMode liner.ModeApplier
}
func newUserInputReader() *userInputReader {
r := new(userInputReader)
// Get the original mode before calling NewLiner.
// This is usually regular "cooked" mode where characters echo.
normalMode, _ := liner.TerminalMode()
// Turn on liner. It switches to raw mode.
r.State = liner.NewLiner()
rawMode, err := liner.TerminalMode()
if err != nil || !liner.TerminalSupported() {
r.supported = false
} else {
r.supported = true
r.normalMode = normalMode
r.rawMode = rawMode
// Switch back to normal mode while we're not prompting.
normalMode.ApplyMode()
}
return r
}
func (r *userInputReader) Prompt(prompt string) (string, error) {
if r.supported {
r.rawMode.ApplyMode()
defer r.normalMode.ApplyMode()
} else {
// liner tries to be smart about printing the prompt
// and doesn't print anything if input is redirected.
// Un-smart it by printing the prompt always.
fmt.Print(prompt)
prompt = ""
defer fmt.Println()
}
return r.State.Prompt(prompt)
}
func (r *userInputReader) PasswordPrompt(prompt string) (passwd string, err error) {
if r.supported {
r.rawMode.ApplyMode()
defer r.normalMode.ApplyMode()
return r.State.PasswordPrompt(prompt)
}
if !r.warned {
fmt.Println("!! Unsupported terminal, password will be echoed.")
r.warned = true
}
// Just as in Prompt, handle printing the prompt here instead of relying on liner.
fmt.Print(prompt)
passwd, err = r.State.Prompt("")
fmt.Println()
return passwd, err
}
func (r *userInputReader) ConfirmPrompt(prompt string) (bool, error) {
prompt = prompt + " [y/N] "
input, err := r.Prompt(prompt)
if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" {
return true, nil
}
return false, err
}

View File

@ -75,8 +75,9 @@ func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
// if password is not given or as null value -> ask user for password
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
fmt.Printf("Unlock account %s\n", account)
if password, err := PromptPassword("Passphrase: ", true); err == nil {
passwd, _ = otto.ToValue(password)
if input, err := Stdin.PasswordPrompt("Passphrase: "); err != nil {
return otto.FalseValue()
passwd, _ = otto.ToValue(input)
} else {
throwJSExeception(err.Error())
}
@ -111,11 +112,11 @@ func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) {
var passwd string
if len(call.ArgumentList) == 0 {
var err error
passwd, err = PromptPassword("Passphrase: ", true)
passwd, err = Stdin.PasswordPrompt("Passphrase: ")
if err != nil {
return otto.FalseValue()
}
passwd2, err := PromptPassword("Repeat passphrase: ", true)
passwd2, err := Stdin.PasswordPrompt("Repeat passphrase: ")
if err != nil {
return otto.FalseValue()
}