aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2017-02-13 21:03:16 +0800
committerGitHub <noreply@github.com>2017-02-13 21:03:16 +0800
commitf8f428cc18c5f70814d7b3937128781bac14bffd (patch)
treed93d285d2ec22bd8ed646695c3db116c69fa3329 /cmd
parente23e86921b55cb1ee2fca6b6fb9ed91f5532f9fd (diff)
parente99c788155ddd754c73d2c81b6051dcbd42e6575 (diff)
downloadgo-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.tar.gz
go-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.tar.zst
go-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.zip
Merge pull request #3592 from karalabe/hw-wallets
accounts: initial support for Ledger hardware wallets
Diffstat (limited to 'cmd')
-rw-r--r--cmd/geth/accountcmd.go49
-rw-r--r--cmd/geth/accountcmd_test.go30
-rw-r--r--cmd/geth/main.go47
-rw-r--r--cmd/gethrpctest/main.go8
-rw-r--r--cmd/swarm/main.go22
-rw-r--r--cmd/utils/flags.go20
6 files changed, 122 insertions, 54 deletions
diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go
index 237af99eb..cd398eadb 100644
--- a/cmd/geth/accountcmd.go
+++ b/cmd/geth/accountcmd.go
@@ -21,6 +21,7 @@ import (
"io/ioutil"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/crypto"
@@ -180,31 +181,36 @@ nodes.
func accountList(ctx *cli.Context) error {
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
- for i, acct := range stack.AccountManager().Accounts() {
- fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
+
+ var index int
+ for _, wallet := range stack.AccountManager().Wallets() {
+ for _, account := range wallet.Accounts() {
+ fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL)
+ index++
+ }
}
return nil
}
// 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)
+func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
+ account, err := utils.MakeAddress(ks, 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)
+ err = ks.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 {
+ if err, ok := err.(*keystore.AmbiguousAddrError); ok {
glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
- return ambiguousAddrRecovery(accman, err, password), password
+ return ambiguousAddrRecovery(ks, err, password), password
}
- if err != accounts.ErrDecrypt {
+ if err != keystore.ErrDecrypt {
// No need to prompt again if the error is not decryption-related.
break
}
@@ -244,15 +250,15 @@ func getPassPhrase(prompt string, confirmation bool, i int, passwords []string)
return password
}
-func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account {
+func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.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(" ", a.URL)
}
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 {
+ if err := ks.Unlock(a, auth); err == nil {
match = &a
break
}
@@ -260,11 +266,11 @@ func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrErro
if match == nil {
utils.Fatalf("None of the listed files could be unlocked.")
}
- fmt.Printf("Your passphrase unlocked %s\n", match.File)
+ fmt.Printf("Your passphrase unlocked %s\n", match.URL)
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)
+ fmt.Println(" ", a.URL)
}
}
return *match
@@ -275,7 +281,8 @@ func accountCreate(ctx *cli.Context) error {
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
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 := stack.AccountManager().NewAccount(password)
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ account, err := ks.NewAccount(password)
if err != nil {
utils.Fatalf("Failed to create account: %v", err)
}
@@ -290,9 +297,11 @@ func accountUpdate(ctx *cli.Context) error {
utils.Fatalf("No accounts specified to update")
}
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
- account, oldPassword := unlockAccount(ctx, stack.AccountManager(), ctx.Args().First(), 0, nil)
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+
+ account, oldPassword := unlockAccount(ctx, ks, ctx.Args().First(), 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
- if err := stack.AccountManager().Update(account, oldPassword, newPassword); err != nil {
+ if err := ks.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err)
}
return nil
@@ -310,7 +319,9 @@ func importWallet(ctx *cli.Context) error {
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
- acct, err := stack.AccountManager().ImportPreSaleKey(keyJson, passphrase)
+
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ acct, err := ks.ImportPreSaleKey(keyJson, passphrase)
if err != nil {
utils.Fatalf("%v", err)
}
@@ -329,7 +340,9 @@ func accountImport(ctx *cli.Context) error {
}
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
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 := stack.AccountManager().ImportECDSA(key, passphrase)
+
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ acct, err := ks.ImportECDSA(key, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
}
diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go
index 113df983e..679a7ec30 100644
--- a/cmd/geth/accountcmd_test.go
+++ b/cmd/geth/accountcmd_test.go
@@ -35,7 +35,7 @@ import (
func tmpDatadirWithKeystore(t *testing.T) string {
datadir := tmpdir(t)
keystore := filepath.Join(datadir, "keystore")
- source := filepath.Join("..", "..", "accounts", "testdata", "keystore")
+ source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore")
if err := cp.CopyAll(keystore, source); err != nil {
t.Fatal(err)
}
@@ -53,15 +53,15 @@ func TestAccountList(t *testing.T) {
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
+Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
+Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa
+Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.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
+Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
+Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa
+Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz
`)
}
}
@@ -230,7 +230,7 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
}
func TestUnlockFlagAmbiguous(t *testing.T) {
- store := filepath.Join("..", "..", "accounts", "testdata", "dupes")
+ store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
geth := runGeth(t,
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
@@ -247,12 +247,12 @@ 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"}}
+ keystore://{{keypath "1"}}
+ keystore://{{keypath "2"}}
Testing your passphrase against all of them...
-Your passphrase unlocked {{keypath "1"}}
+Your passphrase unlocked keystore://{{keypath "1"}}
In order to avoid this warning, you need to remove the following duplicate key files:
- {{keypath "2"}}
+ keystore://{{keypath "2"}}
`)
geth.expectExit()
@@ -267,7 +267,7 @@ In order to avoid this warning, you need to remove the following duplicate key f
}
func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
- store := filepath.Join("..", "..", "accounts", "testdata", "dupes")
+ store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes")
geth := runGeth(t,
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
@@ -283,8 +283,8 @@ 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"}}
+ keystore://{{keypath "1"}}
+ keystore://{{keypath "2"}}
Testing your passphrase against all of them...
Fatal: None of the listed files could be unlocked.
`)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d7e4cc7b5..c19770bfa 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -25,11 +25,14 @@ import (
"strings"
"time"
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/contracts/release"
"github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
@@ -245,14 +248,50 @@ func startNode(ctx *cli.Context, stack *node.Node) {
utils.StartNode(stack)
// Unlock any account specifically requested
- accman := stack.AccountManager()
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+
passwords := utils.MakePasswordList(ctx)
- accounts := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
- for i, account := range accounts {
+ unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
+ for i, account := range unlocks {
if trimmed := strings.TrimSpace(account); trimmed != "" {
- unlockAccount(ctx, accman, trimmed, i, passwords)
+ unlockAccount(ctx, ks, trimmed, i, passwords)
}
}
+ // Register wallet event handlers to open and auto-derive wallets
+ events := make(chan accounts.WalletEvent, 16)
+ stack.AccountManager().Subscribe(events)
+
+ go func() {
+ // Create an chain state reader for self-derivation
+ rpcClient, err := stack.Attach()
+ if err != nil {
+ utils.Fatalf("Failed to attach to self: %v", err)
+ }
+ stateReader := ethclient.NewClient(rpcClient)
+
+ // Open and self derive any wallets already attached
+ for _, wallet := range stack.AccountManager().Wallets() {
+ if err := wallet.Open(""); err != nil {
+ glog.V(logger.Warn).Infof("Failed to open wallet %s: %v", wallet.URL(), err)
+ } else {
+ wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
+ }
+ }
+ // Listen for wallet event till termination
+ for event := range events {
+ if event.Arrive {
+ if err := event.Wallet.Open(""); err != nil {
+ glog.V(logger.Info).Infof("New wallet appeared: %s, failed to open: %s", event.Wallet.URL(), err)
+ } else {
+ glog.V(logger.Info).Infof("New wallet appeared: %s, %s", event.Wallet.URL(), event.Wallet.Status())
+ event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
+ }
+ } else {
+ glog.V(logger.Info).Infof("Old wallet dropped: %s", event.Wallet.URL())
+ event.Wallet.Close()
+ }
+ }
+ }()
// Start auxiliary services if enabled
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
var ethereum *eth.Ethereum
diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go
index 850bf8eb2..9e80ad05d 100644
--- a/cmd/gethrpctest/main.go
+++ b/cmd/gethrpctest/main.go
@@ -23,6 +23,7 @@ import (
"os"
"os/signal"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
@@ -99,17 +100,18 @@ func MakeSystemNode(privkey string, test *tests.BlockTest) (*node.Node, error) {
return nil, err
}
// Create the keystore and inject an unlocked account if requested
- accman := stack.AccountManager()
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+
if len(privkey) > 0 {
key, err := crypto.HexToECDSA(privkey)
if err != nil {
return nil, err
}
- a, err := accman.ImportECDSA(key, "")
+ a, err := ks.ImportECDSA(key, "")
if err != nil {
return nil, err
}
- if err := accman.Unlock(a, ""); err != nil {
+ if err := ks.Unlock(a, ""); err != nil {
return nil, err
}
}
diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go
index 7d76d55c1..14adc3b10 100644
--- a/cmd/swarm/main.go
+++ b/cmd/swarm/main.go
@@ -26,6 +26,7 @@ import (
"strings"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
@@ -336,29 +337,36 @@ func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
return key
}
// Otherwise try getting it from the keystore.
- return decryptStoreAccount(stack.AccountManager(), keyid)
+ am := stack.AccountManager()
+ ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+
+ return decryptStoreAccount(ks, keyid)
}
-func decryptStoreAccount(accman *accounts.Manager, account string) *ecdsa.PrivateKey {
+func decryptStoreAccount(ks *keystore.KeyStore, account string) *ecdsa.PrivateKey {
var a accounts.Account
var err error
if common.IsHexAddress(account) {
- a, err = accman.Find(accounts.Account{Address: common.HexToAddress(account)})
- } else if ix, ixerr := strconv.Atoi(account); ixerr == nil {
- a, err = accman.AccountByIndex(ix)
+ a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
+ } else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
+ if accounts := ks.Accounts(); len(accounts) > ix {
+ a = accounts[ix]
+ } else {
+ err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
+ }
} else {
utils.Fatalf("Can't find swarm account key %s", account)
}
if err != nil {
utils.Fatalf("Can't find swarm account key: %v", err)
}
- keyjson, err := ioutil.ReadFile(a.File)
+ keyjson, err := ioutil.ReadFile(a.URL.Path)
if err != nil {
utils.Fatalf("Can't load swarm account key: %v", err)
}
for i := 1; i <= 3; i++ {
passphrase := promptPassphrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i))
- key, err := accounts.DecryptKey(keyjson, passphrase)
+ key, err := keystore.DecryptKey(keyjson, passphrase)
if err == nil {
return key.PrivateKey
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 9ba33df80..92eb05e32 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -30,6 +30,7 @@ import (
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
@@ -587,23 +588,27 @@ func MakeDatabaseHandles() int {
// 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) (accounts.Account, error) {
+func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) {
// If the specified account is a valid address, return it
if common.IsHexAddress(account) {
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 {
+ if err != nil || index < 0 {
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
}
- return accman.AccountByIndex(index)
+ accs := ks.Accounts()
+ if len(accs) <= index {
+ return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
+ }
+ return accs[index], nil
}
// 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()
+func MakeEtherbase(ks *keystore.KeyStore, ctx *cli.Context) common.Address {
+ accounts := ks.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{}
@@ -613,7 +618,7 @@ func MakeEtherbase(accman *accounts.Manager, ctx *cli.Context) common.Address {
return common.Address{}
}
// If the specified etherbase is a valid address, return it
- account, err := MakeAddress(accman, etherbase)
+ account, err := MakeAddress(ks, etherbase)
if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
@@ -721,9 +726,10 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
if networks > 1 {
Fatalf("The %v flags are mutually exclusive", netFlags)
}
+ ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
ethConf := &eth.Config{
- Etherbase: MakeEtherbase(stack.AccountManager(), ctx),
+ Etherbase: MakeEtherbase(ks, ctx),
ChainConfig: MakeChainConfig(ctx, stack),
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
LightMode: ctx.GlobalBool(LightModeFlag.Name),