From bde1d9c9873b59ae6f501e2e3eb30e44037fa96b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 17 Aug 2018 15:24:11 +0200 Subject: [PATCH 1/4] ethapi, node, geth: remove account management --- cmd/geth/accountcmd.go | 379 ------------------------------------ cmd/geth/accountcmd_test.go | 296 ---------------------------- cmd/geth/config.go | 2 - cmd/geth/main.go | 73 ++----- cmd/geth/usage.go | 8 - cmd/swarm/main.go | 8 +- cmd/utils/flags.go | 86 ++------ console/console.go | 31 --- eth/api_backend.go | 11 +- eth/backend.go | 50 ++--- internal/ethapi/api.go | 310 ++++++++--------------------- internal/ethapi/backend.go | 12 +- les/api_backend.go | 6 +- les/backend.go | 4 +- node/config.go | 4 + node/node.go | 35 +--- node/service.go | 2 - 17 files changed, 160 insertions(+), 1157 deletions(-) delete mode 100644 cmd/geth/accountcmd.go delete mode 100644 cmd/geth/accountcmd_test.go diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go deleted file mode 100644 index 071be85397..0000000000 --- a/cmd/geth/accountcmd.go +++ /dev/null @@ -1,379 +0,0 @@ -// 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 . - -package main - -import ( - "fmt" - "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" - "github.com/ethereum/go-ethereum/log" - "gopkg.in/urfave/cli.v1" -) - -var ( - walletCommand = cli.Command{ - Name: "wallet", - Usage: "Manage Ethereum presale wallets", - ArgsUsage: "", - Category: "ACCOUNT COMMANDS", - Description: ` - geth 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.`, - Subcommands: []cli.Command{ - { - - Name: "import", - Usage: "Import Ethereum presale wallet", - ArgsUsage: "", - Action: utils.MigrateFlags(importWallet), - Category: "ACCOUNT COMMANDS", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - Description: ` - geth wallet [options] /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{ - Name: "account", - Usage: "Manage accounts", - Category: "ACCOUNT COMMANDS", - Description: ` - -Manage accounts, list all existing accounts, import a private key into a new -account, create a new account or update an existing account. - -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 /keystore. -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.`, - Subcommands: []cli.Command{ - { - Name: "list", - Usage: "Print summary of existing accounts", - Action: utils.MigrateFlags(accountList), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - }, - Description: ` -Print a short summary of all accounts`, - }, - { - Name: "new", - Usage: "Create a new account", - Action: utils.MigrateFlags(accountCreate), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - Description: ` - geth account new - -Creates a new account and 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: - -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. -`, - }, - { - Name: "update", - Usage: "Update an existing account", - Action: utils.MigrateFlags(accountUpdate), - ArgsUsage: "
", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.LightKDFFlag, - }, - Description: ` - geth account update
- -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: - - geth account update [options]
- -Since only one password can be given, only format update can be performed, -changing your password is only possible interactively. -`, - }, - { - Name: "import", - Usage: "Import a private key into a new account", - Action: utils.MigrateFlags(accountImport), - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.KeyStoreDirFlag, - utils.PasswordFileFlag, - utils.LightKDFFlag, - }, - ArgsUsage: "", - Description: ` - geth account import - -Imports an unencrypted private key from 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: - - geth account import [options] - -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) error { - stack, _ := makeConfigNode(ctx) - 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, 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 = ks.Unlock(account, password) - if err == nil { - log.Info("Unlocked account", "address", account.Address.Hex()) - return account, password - } - if err, ok := err.(*keystore.AmbiguousAddrError); ok { - log.Info("Unlocked account", "address", account.Address.Hex()) - return ambiguousAddrRecovery(ks, err, password), password - } - if err != keystore.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 password 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 := console.Stdin.PromptPassword("Passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase: %v", err) - } - if confirmation { - confirm, err := console.Stdin.PromptPassword("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(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.URL) - } - fmt.Println("Testing your passphrase against all of them...") - var match *accounts.Account - for _, a := range err.Matches { - if err := ks.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.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.URL) - } - } - return *match -} - -// accountCreate creates a new account into the keystore defined by the CLI flags. -func accountCreate(ctx *cli.Context) error { - cfg := gethConfig{Node: defaultNodeConfig()} - // Load config file. - if file := ctx.GlobalString(configFileFlag.Name); file != "" { - if err := loadConfig(file, &cfg); err != nil { - utils.Fatalf("%v", err) - } - } - utils.SetNodeConfig(ctx, &cfg.Node) - scryptN, scryptP, keydir, err := cfg.Node.AccountConfig() - - if err != nil { - utils.Fatalf("Failed to read configuration: %v", err) - } - - password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) - - address, err := keystore.StoreKey(keydir, password, scryptN, scryptP) - - if err != nil { - utils.Fatalf("Failed to create account: %v", err) - } - fmt.Printf("Address: {%x}\n", address) - return nil -} - -// 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) error { - if len(ctx.Args()) == 0 { - utils.Fatalf("No accounts specified to update") - } - stack, _ := makeConfigNode(ctx) - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - for _, addr := range ctx.Args() { - account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) - if err := ks.Update(account, oldPassword, newPassword); err != nil { - utils.Fatalf("Could not update the account: %v", err) - } - } - return nil -} - -func importWallet(ctx *cli.Context) error { - 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) - } - - stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) - - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) - if err != nil { - utils.Fatalf("%v", err) - } - fmt.Printf("Address: {%x}\n", acct.Address) - return nil -} - -func accountImport(ctx *cli.Context) error { - 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("Failed to load the private key: %v", err) - } - stack, _ := makeConfigNode(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)) - - 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) - } - fmt.Printf("Address: {%x}\n", acct.Address) - return nil -} diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go deleted file mode 100644 index 3ea22ccfab..0000000000 --- a/cmd/geth/accountcmd_test.go +++ /dev/null @@ -1,296 +0,0 @@ -// 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 . - -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", "keystore", "testdata", "keystore") - if err := cp.CopyAll(keystore, source); err != nil { - t.Fatal(err) - } - return datadir -} - -func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "account", "list") - geth.ExpectExit() -} - -func TestAccountList(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "list", "--datadir", datadir) - defer geth.ExpectExit() - if runtime.GOOS == "windows" { - geth.Expect(` -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} 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 -`) - } -} - -func TestAccountNew(t *testing.T) { - geth := runGeth(t, "account", "new", "--lightkdf") - 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, "account", "new", "--lightkdf") - 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, "account", "update", - "--datadir", datadir, "--lightkdf", - "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, "wallet", "import", "--lightkdf", "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, "wallet", "import", "--lightkdf", "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", "--maxpeers", "0", "--port", "0", - "--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", - "=0xf466859eAD1932D743d622CB74FC058882E8648A", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - 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", "--maxpeers", "0", "--port", "0", - "--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", "--maxpeers", "0", "--port", "0", - "--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", - "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", - "=0x289d485D9771714CCe91D3393D764E1311907ACc", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - 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", "--maxpeers", "0", "--port", "0", - "--password", "testdata/passwords.txt", "--unlock", "0,2", - "js", "testdata/empty.js") - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", - "=0x289d485D9771714CCe91D3393D764E1311907ACc", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - 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", "--maxpeers", "0", "--port", "0", - "--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", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--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: - keystore://{{keypath "1"}} - keystore://{{keypath "2"}} -Testing your passphrase against all of them... -Your passphrase unlocked keystore://{{keypath "1"}} -In order to avoid this warning, you need to remove the following duplicate key files: - keystore://{{keypath "2"}} -`) - geth.ExpectExit() - - wantMessages := []string{ - "Unlocked account", - "=0xf466859eAD1932D743d622CB74FC058882E8648A", - } - for _, m := range wantMessages { - if !strings.Contains(geth.StderrText(), m) { - t.Errorf("stderr text does not contain %q", m) - } - } -} - -func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { - store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--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: - keystore://{{keypath "1"}} - keystore://{{keypath "2"}} -Testing your passphrase against all of them... -Fatal: None of the listed files could be unlocked. -`) - geth.ExpectExit() -} diff --git a/cmd/geth/config.go b/cmd/geth/config.go index b0749d2329..c0842c4798 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -152,9 +152,7 @@ func enableWhisper(ctx *cli.Context) bool { func makeFullNode(ctx *cli.Context) *node.Node { stack, cfg := makeConfigNode(ctx) - utils.RegisterEthService(stack, &cfg.Eth) - if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) { utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index fae4b57181..07e8b5bda6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -25,21 +25,18 @@ import ( godebug "runtime/debug" "sort" "strconv" - "strings" "time" "github.com/elastic/gosigar" - "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/eth" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/internal/ethapi" ) const ( @@ -54,13 +51,10 @@ var ( // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, - utils.UnlockedAccountFlag, - utils.PasswordFileFlag, utils.BootnodesFlag, utils.BootnodesV4Flag, utils.BootnodesV5Flag, utils.DataDirFlag, - utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.DashboardEnabledFlag, utils.DashboardAddrFlag, @@ -73,6 +67,7 @@ var ( utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsOnDiskFlag, utils.TxPoolLocalsFlag, + utils.ExternalSignerFlag, utils.TxPoolNoLocalsFlag, utils.TxPoolJournalFlag, utils.TxPoolRejournalFlag, @@ -183,9 +178,6 @@ func init() { dumpCommand, // See monitorcmd.go: monitorCommand, - // See accountcmd.go: - accountCommand, - walletCommand, // See consolecmd.go: consoleCommand, attachCommand, @@ -276,60 +268,19 @@ func geth(ctx *cli.Context) error { func startNode(ctx *cli.Context, stack *node.Node) { debug.Memsize.Add("node", stack) + // Start account management + if extEndpoint := ctx.GlobalString(utils.ExternalSignerFlag.Name); extEndpoint != ""{ + extApi, err := ethapi.NewExternalSigner(extEndpoint) + if err != nil{ + utils.Fatalf("Could not connect to external signer at %v: %v" , extEndpoint, err) + } + log.Info("External signer connected", "url", extEndpoint) + stack.SetExternalAPI(extApi) + } + // Start up the node itself utils.StartNode(stack) - // Unlock any account specifically requested - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - passwords := utils.MakePasswordList(ctx) - unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") - for i, account := range unlocks { - if trimmed := strings.TrimSpace(account); trimmed != "" { - 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 a 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 any wallets already attached - for _, wallet := range stack.AccountManager().Wallets() { - if err := wallet.Open(""); err != nil { - log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) - } - } - // Listen for wallet event till termination - for event := range events { - switch event.Kind { - case accounts.WalletArrived: - if err := event.Wallet.Open(""); err != nil { - log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) - } - case accounts.WalletOpened: - status, _ := event.Wallet.Status() - log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - - derivationPath := accounts.DefaultBaseDerivationPath - if event.Wallet.URL().Scheme == "ledger" { - derivationPath = accounts.DefaultLedgerBaseDerivationPath - } - event.Wallet.SelfDerive(derivationPath, stateReader) - - case accounts.WalletDropped: - log.Info("Old wallet dropped", "url", event.Wallet.URL()) - event.Wallet.Close() - } - } - }() // Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { // Mining only makes sense if a full Ethereum node is running diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 8b0491ce3c..2c6fa3829a 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -69,7 +69,6 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ configFileFlag, utils.DataDirFlag, - utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.NetworkIdFlag, utils.TestnetFlag, @@ -136,13 +135,6 @@ var AppHelpFlagGroups = []flagGroup{ utils.TrieCacheGenFlag, }, }, - { - Name: "ACCOUNT", - Flags: []cli.Flag{ - utils.UnlockedAccountFlag, - utils.PasswordFileFlag, - }, - }, { Name: "API AND CONSOLE", Flags: []cli.Flag{ diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index 88e6b0b0be..28e78f920b 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -565,7 +565,6 @@ pv(1) tool to get a progress bar: utils.NATFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, - utils.PasswordFileFlag, // bzzd-specific flags CorsStringFlag, EnsAPIFlag, @@ -722,11 +721,8 @@ func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.Pr log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey)) return key } - // Otherwise try getting it from the keystore. - am := stack.AccountManager() - ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx)) + utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT) + return nil } // getPrivKey returns the private key of the specified bzzaccount diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 429c2bbb9b..ebeca0bbab 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,16 +20,13 @@ package utils import ( "crypto/ecdsa" "fmt" - "io/ioutil" "math/big" "os" "path/filepath" - "strconv" "strings" "time" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/consensus" @@ -379,17 +376,15 @@ var ( Usage: "Disable remote sealing verification", } // Account settings - UnlockedAccountFlag = cli.StringFlag{ - Name: "unlock", - Usage: "Comma separated list of accounts to unlock", - Value: "", - } PasswordFileFlag = cli.StringFlag{ Name: "password", Usage: "Password file to use for non-interactive password input", + } + ExternalSignerFlag = cli.StringFlag{ + Name: "signer", + Usage: "External signer (url or path to ipc file)", Value: "", } - VMEnableDebugFlag = cli.BoolFlag{ Name: "vmdebug", Usage: "Record information useful for VM and contract debugging", @@ -836,32 +831,21 @@ 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(ks *keystore.KeyStore, account string) (accounts.Account, error) { +func MakeAddress(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 + ma, err := common.NewMixedcaseAddressFromString(account) + if err != nil { + return accounts.Account{}, fmt.Errorf("Not valid hex address (%v): %v", account, err) } - // Otherwise try to interpret the account as a keystore index - index, err := strconv.Atoi(account) - if err != nil || index < 0 { - return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) + if !ma.ValidChecksum() { + return accounts.Account{}, fmt.Errorf("Invalid checksum on address %v", account) } - log.Warn("-------------------------------------------------------------------") - log.Warn("Referring to accounts by order in the keystore folder is dangerous!") - log.Warn("This functionality is deprecated and will be removed in the future!") - log.Warn("Please use explicit addresses! (can search via `geth account list`)") - log.Warn("-------------------------------------------------------------------") - - 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 + return accounts.Account{Address: common.HexToAddress(account)}, nil } // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. -func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { +func setEtherbase(ctx *cli.Context, cfg *eth.Config) { // Extract the current etherbase, new flag overriding legacy one var etherbase string if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) { @@ -872,7 +856,7 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { } // Convert the etherbase into an address and configure it if etherbase != "" { - account, err := MakeAddress(ks, etherbase) + account, err := MakeAddress(etherbase) if err != nil { Fatalf("Invalid miner etherbase: %v", err) } @@ -880,24 +864,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { } } -// MakePasswordList reads password lines from the file specified by the global --password flag. -func MakePasswordList(ctx *cli.Context) []string { - path := ctx.GlobalString(PasswordFileFlag.Name) - if path == "" { - 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 -} - func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setNodeKey(ctx, cfg) setNAT(ctx, cfg) @@ -1128,8 +1094,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag) checkExclusive(ctx, LightServFlag, SyncModeFlag, "light") - ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - setEtherbase(ctx, ks, cfg) + setEtherbase(ctx, cfg) setGPO(ctx, &cfg.GPO) setTxPool(ctx, &cfg.TxPool) setEthash(ctx, cfg) @@ -1223,24 +1188,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.NetworkId = 1337 } // Create new developer account or reuse existing one - var ( - developer accounts.Account - err error - ) - if accs := ks.Accounts(); len(accs) > 0 { - developer = ks.Accounts()[0] - } else { - developer, err = ks.NewAccount("") - if err != nil { - Fatalf("Failed to create developer account: %v", err) - } - } - if err := ks.Unlock(developer, ""); err != nil { - Fatalf("Failed to unlock developer account: %v", err) - } - log.Info("Using developer account", "address", developer.Address) - - cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) + // TODO @holiman + Fatalf("Failed to create developer account: not implemented") + //cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address) if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { cfg.MinerGasPrice = big.NewInt(1) } @@ -1267,7 +1217,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) { }) } else { err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - fullNode, err := eth.New(ctx, cfg) + fullNode, err := eth.New(ctx, cfg, stack.ExternalSignerAPI()) if fullNode != nil && cfg.LightServ > 0 { ls, _ := les.NewLesServer(fullNode, cfg) fullNode.AddLesServer(ls) diff --git a/console/console.go b/console/console.go index 3c397f8006..c0fe72fe68 100644 --- a/console/console.go +++ b/console/console.go @@ -159,37 +159,6 @@ func (c *Console) init(preload []string) error { // Initialize the global name register (disabled for now) //c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) - // If the console is in interactive mode, instrument password related methods to query the user - if c.prompter != nil { - // Retrieve the account management object to instrument - personal, err := c.jsre.Get("personal") - if err != nil { - return err - } - // Override the openWallet, unlockAccount, newAccount and sign methods since - // these require user interaction. Assign these method in the Console the - // original web3 callbacks. These will be called by the jeth.* methods after - // they got the password from the user and send the original web3 request to - // the backend. - if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface - if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil { - return fmt.Errorf("personal.openWallet: %v", err) - } - if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil { - return fmt.Errorf("personal.unlockAccount: %v", err) - } - if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil { - return fmt.Errorf("personal.newAccount: %v", err) - } - if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil { - return fmt.Errorf("personal.sign: %v", err) - } - obj.Set("openWallet", bridge.OpenWallet) - obj.Set("unlockAccount", bridge.UnlockAccount) - obj.Set("newAccount", bridge.NewAccount) - obj.Set("sign", bridge.Sign) - } - } // The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer. admin, err := c.jsre.Get("admin") if err != nil { diff --git a/eth/api_backend.go b/eth/api_backend.go index 8748d444fa..6609d6badf 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -20,7 +20,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" @@ -34,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/internal/ethapi" ) // EthAPIBackend implements ethapi.Backend for full nodes @@ -209,10 +209,6 @@ func (b *EthAPIBackend) EventMux() *event.TypeMux { return b.eth.EventMux() } -func (b *EthAPIBackend) AccountManager() *accounts.Manager { - return b.eth.AccountManager() -} - func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections @@ -223,3 +219,8 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } +func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI { + return b.eth.externalSigner +} + + diff --git a/eth/backend.go b/eth/backend.go index b555b064ad..f53c0c8c0d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -25,7 +25,6 @@ import ( "sync" "sync/atomic" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -77,7 +76,7 @@ type Ethereum struct { eventMux *event.TypeMux engine consensus.Engine - accountManager *accounts.Manager + externalSigner *ethapi.ExternalSignerAPI bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -101,7 +100,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerAPI) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -126,10 +125,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, + externalSigner: api, chainDb: chainDb, chainConfig: chainConfig, eventMux: ctx.EventMux, - accountManager: ctx.AccountManager, engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb), shutdownChan: make(chan bool), networkID: config.NetworkId, @@ -319,18 +318,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) { if etherbase != (common.Address{}) { return etherbase, nil } - if wallets := s.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - etherbase := accounts[0].Address - - s.lock.Lock() - s.etherbase = etherbase - s.lock.Unlock() - - log.Info("Etherbase automatically configured", "address", etherbase) - return etherbase, nil - } - } return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") } @@ -426,13 +413,10 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } - if clique, ok := s.engine.(*clique.Clique); ok { - wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) - if wallet == nil || err != nil { - log.Error("Etherbase account unavailable locally", "err", err) - return fmt.Errorf("signer missing: %v", err) - } - clique.Authorize(eb, wallet.SignHash) + if _, ok := s.engine.(*clique.Clique); ok { + //TODO @holiman + log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer not configured: %v", err) } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. @@ -460,16 +444,16 @@ func (s *Ethereum) StopMining() { func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } -func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Engine() consensus.Engine { return s.engine } -func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Engine() consensus.Engine { return s.engine } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } +func (s *Ethereum) NetVersion() uint64 { return s.networkID } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerAPI { return s.externalSigner } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8016ebe3d7..4242a8b106 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -177,53 +177,33 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string { return content } -// PublicAccountAPI provides an API to access accounts managed by this node. -// It offers only methods that can retrieve accounts. -type PublicAccountAPI struct { - am *accounts.Manager -} - -// NewPublicAccountAPI creates a new PublicAccountAPI. -func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI { - return &PublicAccountAPI{am: am} -} - -// Accounts returns the collection of accounts this node manages -func (s *PublicAccountAPI) Accounts() []common.Address { - addresses := make([]common.Address, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - for _, account := range wallet.Accounts() { - addresses = append(addresses, account.Address) - } - } - return addresses -} - // PrivateAccountAPI provides an API to access accounts managed by this node. // It offers methods to create, (un)lock en list accounts. Some methods accept // passwords and are therefore considered private by default. type PrivateAccountAPI struct { - am *accounts.Manager nonceLock *AddrLocker b Backend + extapi *ExternalSignerAPI // external signer, nil if not used } // NewPrivateAccountAPI create a new PrivateAccountAPI. func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { - return &PrivateAccountAPI{ - am: b.AccountManager(), + p := &PrivateAccountAPI{ nonceLock: nonceLock, b: b, + extapi: b.ExternalSigner(), } + return p } // ListAccounts will return a list of addresses for accounts this node manages. func (s *PrivateAccountAPI) ListAccounts() []common.Address { addresses := make([]common.Address, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - for _, account := range wallet.Accounts() { - addresses = append(addresses, account.Address) + if s.extapi != nil { + if accounts, err := s.extapi.listAccounts(); err == nil { + return accounts } + return addresses } return addresses } @@ -237,65 +217,9 @@ type rawWallet struct { Accounts []accounts.Account `json:"accounts,omitempty"` } -// ListWallets will return a list of wallets this node manages. -func (s *PrivateAccountAPI) ListWallets() []rawWallet { - wallets := make([]rawWallet, 0) // return [] instead of nil if empty - for _, wallet := range s.am.Wallets() { - status, failure := wallet.Status() - - raw := rawWallet{ - URL: wallet.URL().String(), - Status: status, - Accounts: wallet.Accounts(), - } - if failure != nil { - raw.Failure = failure.Error() - } - wallets = append(wallets, raw) - } - return wallets -} - -// OpenWallet initiates a hardware wallet opening procedure, establishing a USB -// connection and attempting to authenticate via the provided passphrase. Note, -// the method may return an extra challenge requiring a second open (e.g. the -// Trezor PIN matrix challenge). -func (s *PrivateAccountAPI) OpenWallet(url string, passphrase *string) error { - wallet, err := s.am.Wallet(url) - if err != nil { - return err - } - pass := "" - if passphrase != nil { - pass = *passphrase - } - return wallet.Open(pass) -} - -// DeriveAccount requests a HD wallet to derive a new account, optionally pinning -// it for later reuse. -func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) { - wallet, err := s.am.Wallet(url) - if err != nil { - return accounts.Account{}, err - } - derivPath, err := accounts.ParseDerivationPath(path) - if err != nil { - return accounts.Account{}, err - } - if pin == nil { - pin = new(bool) - } - return wallet.Derive(derivPath, *pin) -} - // NewAccount will create a new account and returns the address for the new account. func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { - acc, err := fetchKeystore(s.am).NewAccount(password) - if err == nil { - return acc.Address, nil - } - return common.Address{}, err + return s.extapi.newAccount() } // fetchKeystore retrives the encrypted keystore from the account manager. @@ -306,58 +230,30 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { // ImportRawKey stores the given hex encoded ECDSA key into the key directory, // encrypting it with the passphrase. func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) { - key, err := crypto.HexToECDSA(privkey) - if err != nil { - return common.Address{}, err - } - acc, err := fetchKeystore(s.am).ImportECDSA(key, password) - return acc.Address, err + return common.Address{}, errors.New("Importing of raw keys is disabled when external signer is used.") } // UnlockAccount will unlock the account associated with the given address with // the given password for duration seconds. If duration is nil it will use a // default of 300 seconds. It returns an indication if the account was unlocked. func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) { - const max = uint64(time.Duration(math.MaxInt64) / time.Second) - var d time.Duration - if duration == nil { - d = 300 * time.Second - } else if *duration > max { - return false, errors.New("unlock duration too large") - } else { - d = time.Duration(*duration) * time.Second - } - err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d) - return err == nil, err + return false, errors.New("Unlock is disabled when external signer is used.") } // LockAccount will lock the account associated with the given address when it's unlocked. func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { - return fetchKeystore(s.am).Lock(addr) == nil + return false } // signTransactions sets defaults and signs the given transaction // NOTE: the caller needs to ensure that the nonceLock is held, if applicable, // and release it after the transaction has been submitted to the tx pool func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - wallet, err := s.am.Find(account) - if err != nil { - return nil, err - } // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() - - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - return wallet.SignTxWithPassphrase(account, passwd, tx, chainID) + return s.extapi.signTransaction(ctx, args) } // SendTransaction will create a transaction from the given arguments and @@ -416,32 +312,6 @@ func signHash(data []byte) []byte { return crypto.Keccak256([]byte(msg)) } -// Sign calculates an Ethereum ECDSA signature for: -// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -// -// The key used to calculate the signature is decrypted with the given password. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign -func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Assemble sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data)) - if err != nil { - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - // EcRecover returns the address for the account that was used to create the signature. // Note, this function is compatible with eth_sign and personal_sign. As such it recovers // the address of: @@ -471,7 +341,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt // SignAndSendTransaction was renamed to SendTransaction. This method is deprecated // and will be removed in the future. It primary goal is to give clients time to update. func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { - return s.SendTransaction(ctx, args, passwd) + return common.Hash{}, errors.New("SignAndSend is deprecated, please use sendTransaction instead") } // PublicBlockChainAPI provides an API to access the Ethereum blockchain. @@ -618,15 +488,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr if state == nil || err != nil { return nil, 0, false, err } - // Set sender address or use a default if none specified + // Set sender address addr := args.From - if addr == (common.Address{}) { - if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { - if accounts := wallets[0].Accounts(); len(accounts) > 0 { - addr = accounts[0].Address - } - } - } // Set default gas & gas price if none were set gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt() if gas == 0 { @@ -943,11 +806,12 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend nonceLock *AddrLocker + extapi *ExternalSignerAPI // external signer, nil if not used } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{b, nonceLock} + return &PublicTransactionPoolAPI{b, nonceLock, b.ExternalSigner()} } // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. @@ -1090,23 +954,6 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha return fields, nil } -// sign is a helper function that signs a transaction with the private key of the given address. -func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Request the wallet to sign the transaction - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - return wallet.SignTx(account, tx, chainID) -} - // SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. type SendTxArgs struct { From common.Address `json:"from"` @@ -1197,34 +1044,20 @@ func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c // SendTransaction creates a transaction for the given argument, sign it and submit it to the // transaction pool. func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { - - // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return common.Hash{}, err + if s.extapi == nil { + return common.Hash{}, errors.New("No external signer configured") } - if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. s.nonceLock.LockAddr(args.From) defer s.nonceLock.UnlockAddr(args.From) } - // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } - // Assemble the transaction and sign with the wallet - tx := args.toTransaction() - - var chainID *big.Int - if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { - chainID = config.ChainID - } - signed, err := wallet.SignTx(account, tx, chainID) + signed, err := s.extapi.signTransaction(ctx, args) if err != nil { return common.Hash{}, err } @@ -1251,19 +1084,7 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr} - - wallet, err := s.b.AccountManager().Find(account) - if err != nil { - return nil, err - } - // Sign the requested hash with the wallet - signature, err := wallet.SignHash(account, signHash(data)) - if err == nil { - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - } - return signature, err + return nil, errors.New("Sign data not implemented") } // SignTransactionResult represents a RLP encoded signed transaction. @@ -1288,7 +1109,13 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - tx, err := s.sign(args.From, args.toTransaction()) + var ( + tx *types.Transaction + err error + ) + + tx, err = s.extapi.signTransaction(ctx, args) + if err != nil { return nil, err } @@ -1302,28 +1129,9 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) { - pending, err := s.b.GetPoolTransactions() - if err != nil { - return nil, err - } - accounts := make(map[common.Address]struct{}) - for _, wallet := range s.b.AccountManager().Wallets() { - for _, account := range wallet.Accounts() { - accounts[account.Address] = struct{}{} - } - } - transactions := make([]*RPCTransaction, 0, len(pending)) - for _, tx := range pending { - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - if _, exists := accounts[from]; exists { - transactions = append(transactions, newRPCPendingTransaction(tx)) - } - } - return transactions, nil + return nil, errors.New("Not implemented") + // TODO @holiman, make this call take a list of addresses, and show the penading transactions that are from that + // set of addresses } // Resend accepts an existing transaction and a new gas price and limit. It will remove @@ -1340,7 +1148,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if err != nil { return common.Hash{}, err } - + var ( + signedTx *types.Transaction + ) for _, p := range pending { var signer types.Signer = types.HomesteadSigner{} if p.Protected() { @@ -1356,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction()) + signedTx, err = s.extapi.signTransaction(ctx, sendArgs) if err != nil { return common.Hash{}, err } @@ -1489,3 +1299,53 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } + +// ExternalSignerAPI provides an API to interact with an external signer (clef) +// It proxies request to the external signer while forwarding relevant +// request headers +type ExternalSignerAPI struct { + client *rpc.Client +} + +func NewExternalSigner(endpoint string) (*ExternalSignerAPI, error) { + client, err := rpc.DialHTTP(endpoint) + if err != nil { + return nil, err + } + return &ExternalSignerAPI{ + client: client, + }, nil +} + +func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { + if api == nil{ + return nil, errors.New("External API not initialized") + } + res := SignTransactionResult{} + if err := api.client.Call(&res, "account_signTransaction", args); err != nil { + return nil, err + } + return res.Tx, nil +} +func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { + if api == nil{ + return []common.Address{}, errors.New("External API not initialized") + } + var res []common.Address + if err := api.client.Call(&res, "account_listAccounts"); err != nil { + return nil, err + } + return res, nil +} +func (api *ExternalSignerAPI) newAccount() (common.Address, error) { + if api == nil{ + return common.Address{}, errors.New("External API not initialized") + } + var res accounts.Account + + if err := api.client.Call(&res, "account_new"); err != nil { + return res.Address, nil + } + return common.Address{}, nil + +} diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index c9ffe230c6..2e785b853c 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -21,7 +21,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -43,8 +42,8 @@ type Backend interface { SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database EventMux() *event.TypeMux - AccountManager() *accounts.Manager - + ExternalSigner() *ExternalSignerAPI + // BlockChain API SetHead(number uint64) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) @@ -103,12 +102,7 @@ func GetAPIs(apiBackend Backend) []rpc.API { Namespace: "debug", Version: "1.0", Service: NewPrivateDebugAPI(apiBackend), - }, { - Namespace: "eth", - Version: "1.0", - Service: NewPublicAccountAPI(apiBackend.AccountManager()), - Public: true, - }, { + },{ Namespace: "personal", Version: "1.0", Service: NewPrivateAccountAPI(apiBackend, nonceLock), diff --git a/les/api_backend.go b/les/api_backend.go index aa748a4ea1..2e1dfc5342 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -20,7 +20,6 @@ import ( "context" "math/big" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" @@ -36,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/internal/ethapi" ) type LesApiBackend struct { @@ -183,8 +183,8 @@ func (b *LesApiBackend) EventMux() *event.TypeMux { return b.eth.eventMux } -func (b *LesApiBackend) AccountManager() *accounts.Manager { - return b.eth.accountManager +func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{ + return b.eth.externalSigner } func (b *LesApiBackend) BloomStatus() (uint64, uint64) { diff --git a/les/backend.go b/les/backend.go index a3474a6830..5ebcf977da 100644 --- a/les/backend.go +++ b/les/backend.go @@ -22,7 +22,6 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -69,7 +68,7 @@ type LightEthereum struct { eventMux *event.TypeMux engine consensus.Engine - accountManager *accounts.Manager + externalSigner *ethapi.ExternalSignerAPI networkId uint64 netRPCService *ethapi.PublicNetAPI @@ -101,7 +100,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { eventMux: ctx.EventMux, peers: peers, reqDist: newRequestDistributor(peers, quitSync), - accountManager: ctx.AccountManager, engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb), shutdownChan: make(chan bool), networkId: config.NetworkId, diff --git a/node/config.go b/node/config.go index 8f10f4f613..88cbe530c3 100644 --- a/node/config.go +++ b/node/config.go @@ -86,6 +86,10 @@ type Config struct { // NoUSB disables hardware wallet monitoring and connectivity. NoUSB bool `toml:",omitempty"` + // ExternalSigner path/url to external signer. Implicitly disables native account + // management + ExternalSigner string `toml:",omitempty"` + // IPCPath is the requested location to place the IPC endpoint. If the path is // a simple file name, it is placed inside the data directory (or on the root // pipe path on Windows), whereas if it's a resolvable path name (absolute or diff --git a/node/node.go b/node/node.go index ada3837217..60923b2c07 100644 --- a/node/node.go +++ b/node/node.go @@ -26,7 +26,6 @@ import ( "strings" "sync" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/debug" @@ -34,15 +33,15 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/prometheus/prometheus/util/flock" + "github.com/ethereum/go-ethereum/internal/ethapi" ) // Node is a container on which services can be registered. type Node struct { eventmux *event.TypeMux // Event multiplexer used between the services of a stack config *Config - accman *accounts.Manager + extapi *ethapi.ExternalSignerAPI// If non-empty, the ipc-path or http(s)-url to an external signer - ephemeralKeystore string // if non-empty, the key directory that will be removed by Stop instanceDirLock flock.Releaser // prevents concurrent use of instance directory serverConfig p2p.Config @@ -97,20 +96,12 @@ func New(conf *Config) (*Node, error) { if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } - // Ensure that the AccountManager method works before the node has started. - // We rely on this in cmd/geth. - am, ephemeralKeystore, err := makeAccountManager(conf) - if err != nil { - return nil, err - } if conf.Logger == nil { conf.Logger = log.New() } // Note: any interaction with Config that would create/touch files // in the data directory or instance directory is delayed until Start. return &Node{ - accman: am, - ephemeralKeystore: ephemeralKeystore, config: conf, serviceFuncs: []ServiceConstructor{}, ipcEndpoint: conf.IPCEndpoint(), @@ -173,7 +164,6 @@ func (n *Node) Start() error { config: n.config, services: make(map[reflect.Type]Service), EventMux: n.eventmux, - AccountManager: n.accman, } for kind, s := range services { // copy needed for threaded access ctx.services[kind] = s @@ -435,18 +425,9 @@ func (n *Node) Stop() error { // unblock n.Wait close(n.stop) - // Remove the keystore if it was created ephemerally. - var keystoreErr error - if n.ephemeralKeystore != "" { - keystoreErr = os.RemoveAll(n.ephemeralKeystore) - } - if len(failure.Services) > 0 { return failure } - if keystoreErr != nil { - return keystoreErr - } return nil } @@ -537,11 +518,6 @@ func (n *Node) InstanceDir() string { return n.config.instanceDir() } -// AccountManager retrieves the account manager used by the protocol stack. -func (n *Node) AccountManager() *accounts.Manager { - return n.accman -} - // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack. func (n *Node) IPCEndpoint() string { return n.ipcEndpoint @@ -578,6 +554,13 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } +func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerAPI){ + n.extapi = api +} + +func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerAPI{ + return n.extapi +} // apis returns the collection of RPC descriptors this node offers. func (n *Node) apis() []rpc.API { return []rpc.API{ diff --git a/node/service.go b/node/service.go index 6a96d9b1e1..e2e38c5542 100644 --- a/node/service.go +++ b/node/service.go @@ -19,7 +19,6 @@ package node import ( "reflect" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" @@ -33,7 +32,6 @@ type ServiceContext struct { config *Config services map[reflect.Type]Service // Index of the already constructed services EventMux *event.TypeMux // Event multiplexer used for decoupled notifications - AccountManager *accounts.Manager // Account manager created by the node. } // OpenDatabase opens an existing database with the given name (or creates one From 8e0bc14235cba2b60c804555fa1e1fa28b2a0041 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 29 Aug 2018 15:22:08 +0200 Subject: [PATCH 2/4] signer, clef: implement signData to handle e.g. clique headers --- cmd/clef/extapi_changelog.md | 7 +++ cmd/clef/main.go | 35 ++++++++++- signer/core/api.go | 116 +++++++++++++++++++++++++++++------ signer/core/api_test.go | 6 +- signer/core/auditlog.go | 10 +-- 5 files changed, 146 insertions(+), 28 deletions(-) diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index 6c2c3e8194..55b9f0995b 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -1,5 +1,12 @@ ### Changelog for external API +### 5.0.0 + +* The external method `accounts_Sign(address, data)` was replaced with `accounts_signData(contentType, address, data)`. +The addition of `contentType` makes it possible to use the method for different types of objects, such as + * signing clique headers, + * signing [ERC-712](https://eips.ethereum.org/EIPS/eip-712) typed data structures (not yet implemented) + #### 4.0.0 * The external `account_Ecrecover`-method was removed. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 6098b1ac21..73ec166436 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -39,14 +39,17 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core" "github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/storage" "gopkg.in/urfave/cli.v1" + "math/big" ) // ExternalAPIVersion -- see extapi_changelog.md @@ -631,10 +634,38 @@ func testExternalUI(api *core.SignerAPI) { } var err error + cliqueHeader := types.Header{ + common.HexToHash("0000H45H"), + common.HexToHash("0000H45H"), + common.HexToAddress("0000H45H"), + common.HexToHash("0000H00H"), + common.HexToHash("0000H45H"), + common.HexToHash("0000H45H"), + types.Bloom{}, + big.NewInt(1337), + big.NewInt(1337), + 1338, + 1338, + big.NewInt(1338), + []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), + common.HexToHash("0x0000H45H"), + types.BlockNonce{}, + } + cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) + if err != nil { + utils.Fatalf("Should not error: %v", err) + } + addr, err:= common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + if err != nil { + utils.Fatalf("Should not error: %v", err) + } + _, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp) + checkErr("SignData", err) + _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) checkErr("SignTransaction", err) - _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) - checkErr("Sign", err) + //_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) + //checkErr("Sign", err) _, err = api.List(ctx) checkErr("List", err) _, err = api.New(ctx) diff --git a/signer/core/api.go b/signer/core/api.go index 2b96cdb5f3..1265cba713 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -30,10 +30,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "mime" ) // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive @@ -48,7 +51,9 @@ type ExternalAPI interface { // SignTransaction request to sign the specified transaction SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) // Sign - request to sign the given data (plus prefix) - Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // EcRecover - request to perform ecrecover + //EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) // Export - request to export an account Export(ctx context.Context, addr common.Address) (json.RawMessage, error) // Import - request to import an account @@ -170,11 +175,12 @@ type ( NewPassword string `json:"new_password"` } SignDataRequest struct { - Address common.MixedcaseAddress `json:"address"` - Rawdata hexutil.Bytes `json:"raw_data"` - Message string `json:"message"` - Hash hexutil.Bytes `json:"hash"` - Meta Metadata `json:"meta"` + Address common.MixedcaseAddress `json:"address"` + Rawdata hexutil.Bytes `json:"raw_data"` + Message string `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` + ContentType string `json:"content_type"` } SignDataResponse struct { Approved bool `json:"approved"` @@ -510,22 +516,96 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// Sign calculates an Ethereum ECDSA signature for: -// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) +// cliqueSigHash returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func cliqueSigHash(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + } + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} + +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return nil, err + } + switch mediaType { + case "application/clique": + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := cliqueSigHash(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + case "text/plain": + // Sign calculates an Ethereum ECDSA signature for: + // keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) + + // In the cases where it matter ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + + sighash, msg := SignHash(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + default: + //TODO! Add a content-type for EIP712 typed data. + return nil, fmt.Errorf("content type '%s' not implemented for signing") + } + return req, nil + +} + +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Depending on the content-type, different types of validations will occur. // // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons. -// -// The key used to calculate the signature is decrypted with the given password. -// -// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign -func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - sighash, msg := SignHash(data) +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + req.Address = addr + req.Meta = MetadataFromContext(ctx) + // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API - req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)} res, err := api.UI.ApproveSignData(req) - if err != nil { return nil, err } @@ -538,8 +618,8 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da if err != nil { return nil, err } - // Assemble sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash) + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) if err != nil { api.UI.ShowError(err.Error()) return nil, err diff --git a/signer/core/api_test.go b/signer/core/api_test.go index a8aa23896e..abe67c9f4d 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -259,7 +259,7 @@ func TestSignData(t *testing.T) { control <- "Y" control <- "wrongpassword" - h, err := api.Sign(context.Background(), a, []byte("EHLO world")) + h, err := api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -267,7 +267,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control <- "No way" - h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if h != nil { t.Errorf("Expected nil-data, got %x", h) } @@ -276,7 +276,7 @@ func TestSignData(t *testing.T) { } control <- "Y" control <- "a_long_password" - h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + h, err = api.SignData(context.Background(), "text/plain", a, []byte("EHLO world")) if err != nil { t.Fatal(err) } diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 1f9c909185..a0fb1b0767 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -63,11 +63,11 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth return res, e } -func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", common.Bytes2Hex(data)) - b, e := l.api.Sign(ctx, addr, data) - l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) +func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", common.Bytes2Hex(data), "content-type", contentType) + b, e := l.api.SignData(ctx, contentType, addr, data) + l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } From 9e3f27fc186572e0ff7f2847413d0cc11f3d47b5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 25 Sep 2018 11:07:18 +0200 Subject: [PATCH 3/4] clique, signer: implement clique signing for clef --- consensus/clique/clique.go | 8 +++--- eth/api_backend.go | 2 +- eth/backend.go | 42 ++++++++++++++++++++------------ internal/ethapi/api.go | 50 ++++++++++++++++++++++++-------------- internal/ethapi/backend.go | 2 +- les/api_backend.go | 2 +- les/backend.go | 2 +- node/node.go | 6 ++--- 8 files changed, 69 insertions(+), 45 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index eae09f91df..af6494c90e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -39,7 +39,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - lru "github.com/hashicorp/golang-lru" + "github.com/hashicorp/golang-lru" ) const ( @@ -136,9 +136,9 @@ var ( errRecentlySigned = errors.New("recently signed") ) -// SignerFn is a signer callback function to request a hash to be signed by a +// SignerFn is a signer callback function to request a block header to be signed by a // backing account. -type SignerFn func(accounts.Account, []byte) ([]byte, error) +type SignerFn func(accounts.Account, *types.Header) ([]byte, error) // sigHash returns the hash which is used as input for the proof-of-authority // signing. It is the hash of the entire header apart from the 65 byte signature @@ -646,7 +646,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } // Sign all the things! - sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) + sighash, err := signFn(accounts.Account{Address: signer}, header) if err != nil { return err } diff --git a/eth/api_backend.go b/eth/api_backend.go index 6609d6badf..b9dd993dc8 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -219,7 +219,7 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } -func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI { +func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerClient { return b.eth.externalSigner } diff --git a/eth/backend.go b/eth/backend.go index f53c0c8c0d..9a15d1f557 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -20,6 +20,7 @@ package eth import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" "math/big" "runtime" "sync" @@ -76,7 +77,7 @@ type Ethereum struct { eventMux *event.TypeMux engine consensus.Engine - externalSigner *ethapi.ExternalSignerAPI + externalSigner *ethapi.ExternalSignerClient bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -100,7 +101,7 @@ func (s *Ethereum) AddLesServer(ls LesServer) { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerAPI) (*Ethereum, error) { +func New(ctx *node.ServiceContext, config *Config, api *ethapi.ExternalSignerClient) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -413,10 +414,19 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } - if _, ok := s.engine.(*clique.Clique); ok { - //TODO @holiman - log.Error("Etherbase account unavailable locally", "err", err) - return fmt.Errorf("signer not configured: %v", err) + if clique, ok := s.engine.(*clique.Clique); ok { + if s.ExternalSigner() != nil { + signerFn := func(a accounts.Account, header *types.Header) ([]byte, error) { + rlpHeader, err := rlp.EncodeToBytes(header) + if err != nil { + return nil, err + } + return s.ExternalSigner().SignCliqueBlock(a.Address, rlpHeader) + } + clique.Authorize(eb, signerFn) + } else { + return fmt.Errorf("external signer not configured") + } } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. @@ -444,16 +454,16 @@ func (s *Ethereum) StopMining() { func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Engine() consensus.Engine { return s.engine } -func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerAPI { return s.externalSigner } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Engine() consensus.Engine { return s.engine } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } +func (s *Ethereum) NetVersion() uint64 { return s.networkID } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } +func (s *Ethereum) ExternalSigner() *ethapi.ExternalSignerClient { return s.externalSigner } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4242a8b106..cccfbe8c7e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -183,7 +183,7 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string { type PrivateAccountAPI struct { nonceLock *AddrLocker b Backend - extapi *ExternalSignerAPI // external signer, nil if not used + extapi *ExternalSignerClient // external signer, nil if not used } // NewPrivateAccountAPI create a new PrivateAccountAPI. @@ -200,7 +200,7 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { func (s *PrivateAccountAPI) ListAccounts() []common.Address { addresses := make([]common.Address, 0) // return [] instead of nil if empty if s.extapi != nil { - if accounts, err := s.extapi.listAccounts(); err == nil { + if accounts, err := s.extapi.ListAccounts(); err == nil { return accounts } return addresses @@ -219,7 +219,7 @@ type rawWallet struct { // NewAccount will create a new account and returns the address for the new account. func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) { - return s.extapi.newAccount() + return s.extapi.NewAccount() } // fetchKeystore retrives the encrypted keystore from the account manager. @@ -253,7 +253,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } - return s.extapi.signTransaction(ctx, args) + return s.extapi.SignTransaction(ctx, args) } // SendTransaction will create a transaction from the given arguments and @@ -806,7 +806,7 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend nonceLock *AddrLocker - extapi *ExternalSignerAPI // external signer, nil if not used + extapi *ExternalSignerClient // external signer, nil if not used } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. @@ -1057,7 +1057,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen if err := args.setDefaults(ctx, s.b); err != nil { return common.Hash{}, err } - signed, err := s.extapi.signTransaction(ctx, args) + signed, err := s.extapi.SignTransaction(ctx, args) if err != nil { return common.Hash{}, err } @@ -1114,7 +1114,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen err error ) - tx, err = s.extapi.signTransaction(ctx, args) + tx, err = s.extapi.SignTransaction(ctx, args) if err != nil { return nil, err @@ -1166,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err = s.extapi.signTransaction(ctx, sendArgs) + signedTx, err = s.extapi.SignTransaction(ctx, sendArgs) if err != nil { return common.Hash{}, err } @@ -1300,24 +1300,24 @@ func (s *PublicNetAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } -// ExternalSignerAPI provides an API to interact with an external signer (clef) +// ExternalSignerClient provides an API to interact with an external signer (clef) // It proxies request to the external signer while forwarding relevant // request headers -type ExternalSignerAPI struct { +type ExternalSignerClient struct { client *rpc.Client } -func NewExternalSigner(endpoint string) (*ExternalSignerAPI, error) { +func NewExternalSigner(endpoint string) (*ExternalSignerClient, error) { client, err := rpc.DialHTTP(endpoint) if err != nil { return nil, err } - return &ExternalSignerAPI{ + return &ExternalSignerClient{ client: client, }, nil } -func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { +func (api *ExternalSignerClient) SignTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { if api == nil{ return nil, errors.New("External API not initialized") } @@ -1327,7 +1327,7 @@ func (api *ExternalSignerAPI) signTransaction(ctx context.Context, args SendTxAr } return res.Tx, nil } -func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { +func (api *ExternalSignerClient) ListAccounts() ([]common.Address, error) { if api == nil{ return []common.Address{}, errors.New("External API not initialized") } @@ -1337,15 +1337,29 @@ func (api *ExternalSignerAPI) listAccounts() ([]common.Address, error) { } return res, nil } -func (api *ExternalSignerAPI) newAccount() (common.Address, error) { +func (api *ExternalSignerClient) NewAccount() (common.Address, error) { if api == nil{ return common.Address{}, errors.New("External API not initialized") } var res accounts.Account if err := api.client.Call(&res, "account_new"); err != nil { - return res.Address, nil + return common.Address{}, err } - return common.Address{}, nil - + return res.Address, nil +} +func (api *ExternalSignerClient) SignCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) { + if api == nil{ + return nil, errors.New("External API not initialized") + } + var sig hexutil.Bytes + if err := api.client.Call(&sig, "account_signData", "application/clique", a, rlpBlock); err != nil { + return nil, err + } + if sig[64] != 27 && sig[64] != 28 { + return nil, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use + + return sig, nil } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 2e785b853c..8312a3a25f 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,7 +42,7 @@ type Backend interface { SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database EventMux() *event.TypeMux - ExternalSigner() *ExternalSignerAPI + ExternalSigner() *ExternalSignerClient // BlockChain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 2e1dfc5342..e65c15008b 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -183,7 +183,7 @@ func (b *LesApiBackend) EventMux() *event.TypeMux { return b.eth.eventMux } -func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{ +func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerClient { return b.eth.externalSigner } diff --git a/les/backend.go b/les/backend.go index 5ebcf977da..41f489db8c 100644 --- a/les/backend.go +++ b/les/backend.go @@ -68,7 +68,7 @@ type LightEthereum struct { eventMux *event.TypeMux engine consensus.Engine - externalSigner *ethapi.ExternalSignerAPI + externalSigner *ethapi.ExternalSignerClient networkId uint64 netRPCService *ethapi.PublicNetAPI diff --git a/node/node.go b/node/node.go index 60923b2c07..4bde7fbb9e 100644 --- a/node/node.go +++ b/node/node.go @@ -40,7 +40,7 @@ import ( type Node struct { eventmux *event.TypeMux // Event multiplexer used between the services of a stack config *Config - extapi *ethapi.ExternalSignerAPI// If non-empty, the ipc-path or http(s)-url to an external signer + extapi *ethapi.ExternalSignerClient // If non-empty, the ipc-path or http(s)-url to an external signer instanceDirLock flock.Releaser // prevents concurrent use of instance directory @@ -554,11 +554,11 @@ func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) } -func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerAPI){ +func (n *Node) SetExternalAPI(api *ethapi.ExternalSignerClient){ n.extapi = api } -func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerAPI{ +func (n *Node) ExternalSignerAPI() *ethapi.ExternalSignerClient { return n.extapi } // apis returns the collection of RPC descriptors this node offers. From b1da7dfecea13d3bfe4841d5853726e33fa985f5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 25 Sep 2018 17:28:46 +0200 Subject: [PATCH 4/4] ethapi: bubble up listaccounts error, remove extapi ref from private acct api --- internal/ethapi/api.go | 22 +++++++++------------- internal/ethapi/backend.go | 4 ++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index cccfbe8c7e..5400eb2c9c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -191,21 +191,17 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI { p := &PrivateAccountAPI{ nonceLock: nonceLock, b: b, - extapi: b.ExternalSigner(), } return p } // ListAccounts will return a list of addresses for accounts this node manages. -func (s *PrivateAccountAPI) ListAccounts() []common.Address { - addresses := make([]common.Address, 0) // return [] instead of nil if empty - if s.extapi != nil { - if accounts, err := s.extapi.ListAccounts(); err == nil { - return accounts - } - return addresses +func (s *PrivateAccountAPI) ListAccounts() ([]common.Address, error) { + if extapi := s.b.ExternalSigner(); extapi != nil { + return extapi.ListAccounts() } - return addresses + // return [] instead of nil if empty + return []common.Address{}, errors.New("external signer not configured") } // rawWallet is a JSON representation of an accounts.Wallet interface, with its @@ -1318,7 +1314,7 @@ func NewExternalSigner(endpoint string) (*ExternalSignerClient, error) { } func (api *ExternalSignerClient) SignTransaction(ctx context.Context, args SendTxArgs) (*types.Transaction, error) { - if api == nil{ + if api == nil { return nil, errors.New("External API not initialized") } res := SignTransactionResult{} @@ -1328,7 +1324,7 @@ func (api *ExternalSignerClient) SignTransaction(ctx context.Context, args SendT return res.Tx, nil } func (api *ExternalSignerClient) ListAccounts() ([]common.Address, error) { - if api == nil{ + if api == nil { return []common.Address{}, errors.New("External API not initialized") } var res []common.Address @@ -1338,7 +1334,7 @@ func (api *ExternalSignerClient) ListAccounts() ([]common.Address, error) { return res, nil } func (api *ExternalSignerClient) NewAccount() (common.Address, error) { - if api == nil{ + if api == nil { return common.Address{}, errors.New("External API not initialized") } var res accounts.Account @@ -1349,7 +1345,7 @@ func (api *ExternalSignerClient) NewAccount() (common.Address, error) { return res.Address, nil } func (api *ExternalSignerClient) SignCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) { - if api == nil{ + if api == nil { return nil, errors.New("External API not initialized") } var sig hexutil.Bytes diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 8312a3a25f..f80e7c2857 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -43,7 +43,7 @@ type Backend interface { ChainDb() ethdb.Database EventMux() *event.TypeMux ExternalSigner() *ExternalSignerClient - + // BlockChain API SetHead(number uint64) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) @@ -102,7 +102,7 @@ func GetAPIs(apiBackend Backend) []rpc.API { Namespace: "debug", Version: "1.0", Service: NewPrivateDebugAPI(apiBackend), - },{ + }, { Namespace: "personal", Version: "1.0", Service: NewPrivateAccountAPI(apiBackend, nonceLock),