mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
ethapi, node, geth: remove account management
This commit is contained in:
parent
4e474c74dc
commit
bde1d9c987
17 changed files with 160 additions and 1157 deletions
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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: "<keyFile>",
|
|
||||||
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 <DATADIR>/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: "<address>",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
utils.DataDirFlag,
|
|
||||||
utils.KeyStoreDirFlag,
|
|
||||||
utils.LightKDFFlag,
|
|
||||||
},
|
|
||||||
Description: `
|
|
||||||
geth account update <address>
|
|
||||||
|
|
||||||
Update an existing account.
|
|
||||||
|
|
||||||
The account is saved in the newest version in encrypted format, you are prompted
|
|
||||||
for a passphrase to unlock the account and another to save the updated file.
|
|
||||||
|
|
||||||
This same command can therefore be used to migrate an account of a deprecated
|
|
||||||
format to the newest format or change the password for an account.
|
|
||||||
|
|
||||||
For non-interactive use the passphrase can be specified with the --password flag:
|
|
||||||
|
|
||||||
geth account update [options] <address>
|
|
||||||
|
|
||||||
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: "<keyFile>",
|
|
||||||
Description: `
|
|
||||||
geth account import <keyfile>
|
|
||||||
|
|
||||||
Imports an unencrypted private key from <keyfile> and creates a new account.
|
|
||||||
Prints the address.
|
|
||||||
|
|
||||||
The keyfile is assumed to contain an unencrypted private key in hexadecimal format.
|
|
||||||
|
|
||||||
The account is saved in encrypted format, you are prompted for a passphrase.
|
|
||||||
|
|
||||||
You must remember this passphrase to unlock your account in the future.
|
|
||||||
|
|
||||||
For non-interactive use the passphrase can be specified with the -password flag:
|
|
||||||
|
|
||||||
geth account import [options] <keyfile>
|
|
||||||
|
|
||||||
Note:
|
|
||||||
As you can directly copy your encrypted accounts to another ethereum instance,
|
|
||||||
this import mechanism is not needed when you transfer an account between
|
|
||||||
nodes.
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func accountList(ctx *cli.Context) 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
|
|
||||||
}
|
|
||||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/cespare/cp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These tests are 'smoke tests' for the account related
|
|
||||||
// subcommands and flags.
|
|
||||||
//
|
|
||||||
// For most tests, the test files from package accounts
|
|
||||||
// are copied into a temporary keystore directory.
|
|
||||||
|
|
||||||
func tmpDatadirWithKeystore(t *testing.T) string {
|
|
||||||
datadir := tmpdir(t)
|
|
||||||
keystore := filepath.Join(datadir, "keystore")
|
|
||||||
source := filepath.Join("..", "..", "accounts", "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()
|
|
||||||
}
|
|
||||||
|
|
@ -152,9 +152,7 @@ func enableWhisper(ctx *cli.Context) bool {
|
||||||
|
|
||||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
stack, cfg := makeConfigNode(ctx)
|
stack, cfg := makeConfigNode(ctx)
|
||||||
|
|
||||||
utils.RegisterEthService(stack, &cfg.Eth)
|
utils.RegisterEthService(stack, &cfg.Eth)
|
||||||
|
|
||||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||||
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,21 +25,18 @@ import (
|
||||||
godebug "runtime/debug"
|
godebug "runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/elastic/gosigar"
|
"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/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/console"
|
"github.com/ethereum/go-ethereum/console"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -54,13 +51,10 @@ var (
|
||||||
// flags that configure the node
|
// flags that configure the node
|
||||||
nodeFlags = []cli.Flag{
|
nodeFlags = []cli.Flag{
|
||||||
utils.IdentityFlag,
|
utils.IdentityFlag,
|
||||||
utils.UnlockedAccountFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
utils.BootnodesFlag,
|
utils.BootnodesFlag,
|
||||||
utils.BootnodesV4Flag,
|
utils.BootnodesV4Flag,
|
||||||
utils.BootnodesV5Flag,
|
utils.BootnodesV5Flag,
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
|
||||||
utils.NoUSBFlag,
|
utils.NoUSBFlag,
|
||||||
utils.DashboardEnabledFlag,
|
utils.DashboardEnabledFlag,
|
||||||
utils.DashboardAddrFlag,
|
utils.DashboardAddrFlag,
|
||||||
|
|
@ -73,6 +67,7 @@ var (
|
||||||
utils.EthashDatasetsInMemoryFlag,
|
utils.EthashDatasetsInMemoryFlag,
|
||||||
utils.EthashDatasetsOnDiskFlag,
|
utils.EthashDatasetsOnDiskFlag,
|
||||||
utils.TxPoolLocalsFlag,
|
utils.TxPoolLocalsFlag,
|
||||||
|
utils.ExternalSignerFlag,
|
||||||
utils.TxPoolNoLocalsFlag,
|
utils.TxPoolNoLocalsFlag,
|
||||||
utils.TxPoolJournalFlag,
|
utils.TxPoolJournalFlag,
|
||||||
utils.TxPoolRejournalFlag,
|
utils.TxPoolRejournalFlag,
|
||||||
|
|
@ -183,9 +178,6 @@ func init() {
|
||||||
dumpCommand,
|
dumpCommand,
|
||||||
// See monitorcmd.go:
|
// See monitorcmd.go:
|
||||||
monitorCommand,
|
monitorCommand,
|
||||||
// See accountcmd.go:
|
|
||||||
accountCommand,
|
|
||||||
walletCommand,
|
|
||||||
// See consolecmd.go:
|
// See consolecmd.go:
|
||||||
consoleCommand,
|
consoleCommand,
|
||||||
attachCommand,
|
attachCommand,
|
||||||
|
|
@ -276,60 +268,19 @@ func geth(ctx *cli.Context) error {
|
||||||
func startNode(ctx *cli.Context, stack *node.Node) {
|
func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
debug.Memsize.Add("node", stack)
|
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
|
// Start up the node itself
|
||||||
utils.StartNode(stack)
|
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
|
// Start auxiliary services if enabled
|
||||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||||
// Mining only makes sense if a full Ethereum node is running
|
// Mining only makes sense if a full Ethereum node is running
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
|
||||||
utils.NoUSBFlag,
|
utils.NoUSBFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.TestnetFlag,
|
utils.TestnetFlag,
|
||||||
|
|
@ -136,13 +135,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.TrieCacheGenFlag,
|
utils.TrieCacheGenFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "ACCOUNT",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
utils.UnlockedAccountFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "API AND CONSOLE",
|
Name: "API AND CONSOLE",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|
|
||||||
|
|
@ -565,7 +565,6 @@ pv(1) tool to get a progress bar:
|
||||||
utils.NATFlag,
|
utils.NATFlag,
|
||||||
utils.IPCDisabledFlag,
|
utils.IPCDisabledFlag,
|
||||||
utils.IPCPathFlag,
|
utils.IPCPathFlag,
|
||||||
utils.PasswordFileFlag,
|
|
||||||
// bzzd-specific flags
|
// bzzd-specific flags
|
||||||
CorsStringFlag,
|
CorsStringFlag,
|
||||||
EnsAPIFlag,
|
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))
|
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
// Otherwise try getting it from the keystore.
|
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
|
||||||
am := stack.AccountManager()
|
return nil
|
||||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
|
||||||
|
|
||||||
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPrivKey returns the private key of the specified bzzaccount
|
// getPrivKey returns the private key of the specified bzzaccount
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,13 @@ package utils
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -379,17 +376,15 @@ var (
|
||||||
Usage: "Disable remote sealing verification",
|
Usage: "Disable remote sealing verification",
|
||||||
}
|
}
|
||||||
// Account settings
|
// Account settings
|
||||||
UnlockedAccountFlag = cli.StringFlag{
|
|
||||||
Name: "unlock",
|
|
||||||
Usage: "Comma separated list of accounts to unlock",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
PasswordFileFlag = cli.StringFlag{
|
PasswordFileFlag = cli.StringFlag{
|
||||||
Name: "password",
|
Name: "password",
|
||||||
Usage: "Password file to use for non-interactive password input",
|
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: "",
|
Value: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
VMEnableDebugFlag = cli.BoolFlag{
|
VMEnableDebugFlag = cli.BoolFlag{
|
||||||
Name: "vmdebug",
|
Name: "vmdebug",
|
||||||
Usage: "Record information useful for VM and contract debugging",
|
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
|
// MakeAddress converts an account specified directly as a hex encoded string or
|
||||||
// a key index in the key store to an internal account representation.
|
// 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 the specified account is a valid address, return it
|
||||||
if common.IsHexAddress(account) {
|
ma, err := common.NewMixedcaseAddressFromString(account)
|
||||||
return accounts.Account{Address: common.HexToAddress(account)}, nil
|
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
|
if !ma.ValidChecksum() {
|
||||||
index, err := strconv.Atoi(account)
|
return accounts.Account{}, fmt.Errorf("Invalid checksum on address %v", account)
|
||||||
if err != nil || index < 0 {
|
|
||||||
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
|
|
||||||
}
|
}
|
||||||
log.Warn("-------------------------------------------------------------------")
|
return accounts.Account{Address: common.HexToAddress(account)}, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setEtherbase retrieves the etherbase either from the directly specified
|
// setEtherbase retrieves the etherbase either from the directly specified
|
||||||
// command line flags or from the keystore if CLI indexed.
|
// 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
|
// Extract the current etherbase, new flag overriding legacy one
|
||||||
var etherbase string
|
var etherbase string
|
||||||
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
|
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
|
// Convert the etherbase into an address and configure it
|
||||||
if etherbase != "" {
|
if etherbase != "" {
|
||||||
account, err := MakeAddress(ks, etherbase)
|
account, err := MakeAddress(etherbase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Invalid miner etherbase: %v", err)
|
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) {
|
func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
setNodeKey(ctx, cfg)
|
setNodeKey(ctx, cfg)
|
||||||
setNAT(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, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
setEtherbase(ctx, cfg)
|
||||||
setEtherbase(ctx, ks, cfg)
|
|
||||||
setGPO(ctx, &cfg.GPO)
|
setGPO(ctx, &cfg.GPO)
|
||||||
setTxPool(ctx, &cfg.TxPool)
|
setTxPool(ctx, &cfg.TxPool)
|
||||||
setEthash(ctx, cfg)
|
setEthash(ctx, cfg)
|
||||||
|
|
@ -1223,24 +1188,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
cfg.NetworkId = 1337
|
cfg.NetworkId = 1337
|
||||||
}
|
}
|
||||||
// Create new developer account or reuse existing one
|
// Create new developer account or reuse existing one
|
||||||
var (
|
// TODO @holiman
|
||||||
developer accounts.Account
|
Fatalf("Failed to create developer account: not implemented")
|
||||||
err error
|
//cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||||
)
|
|
||||||
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)
|
|
||||||
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
||||||
cfg.MinerGasPrice = big.NewInt(1)
|
cfg.MinerGasPrice = big.NewInt(1)
|
||||||
}
|
}
|
||||||
|
|
@ -1267,7 +1217,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
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 {
|
if fullNode != nil && cfg.LightServ > 0 {
|
||||||
ls, _ := les.NewLesServer(fullNode, cfg)
|
ls, _ := les.NewLesServer(fullNode, cfg)
|
||||||
fullNode.AddLesServer(ls)
|
fullNode.AddLesServer(ls)
|
||||||
|
|
|
||||||
|
|
@ -159,37 +159,6 @@ func (c *Console) init(preload []string) error {
|
||||||
// Initialize the global name register (disabled for now)
|
// Initialize the global name register (disabled for now)
|
||||||
//c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
|
//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.
|
// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
|
||||||
admin, err := c.jsre.Get("admin")
|
admin, err := c.jsre.Get("admin")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -34,6 +33,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EthAPIBackend implements ethapi.Backend for full nodes
|
// EthAPIBackend implements ethapi.Backend for full nodes
|
||||||
|
|
@ -209,10 +209,6 @@ func (b *EthAPIBackend) EventMux() *event.TypeMux {
|
||||||
return b.eth.EventMux()
|
return b.eth.EventMux()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) AccountManager() *accounts.Manager {
|
|
||||||
return b.eth.AccountManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
|
func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
|
||||||
sections, _, _ := b.eth.bloomIndexer.Sections()
|
sections, _, _ := b.eth.bloomIndexer.Sections()
|
||||||
return params.BloomBitsBlocks, 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)
|
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (b *EthAPIBackend) ExternalSigner() *ethapi.ExternalSignerAPI {
|
||||||
|
return b.eth.externalSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -77,7 +76,7 @@ type Ethereum struct {
|
||||||
|
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
accountManager *accounts.Manager
|
externalSigner *ethapi.ExternalSignerAPI
|
||||||
|
|
||||||
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||||
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
|
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
|
// New creates a new Ethereum object (including the
|
||||||
// initialisation of the common Ethereum object)
|
// 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
|
// Ensure configuration values are compatible and sane
|
||||||
if config.SyncMode == downloader.LightSync {
|
if config.SyncMode == downloader.LightSync {
|
||||||
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
|
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{
|
eth := &Ethereum{
|
||||||
config: config,
|
config: config,
|
||||||
|
externalSigner: api,
|
||||||
chainDb: chainDb,
|
chainDb: chainDb,
|
||||||
chainConfig: chainConfig,
|
chainConfig: chainConfig,
|
||||||
eventMux: ctx.EventMux,
|
eventMux: ctx.EventMux,
|
||||||
accountManager: ctx.AccountManager,
|
|
||||||
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb),
|
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, config.MinerNoverify, chainDb),
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
networkID: config.NetworkId,
|
networkID: config.NetworkId,
|
||||||
|
|
@ -319,18 +318,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
||||||
if etherbase != (common.Address{}) {
|
if etherbase != (common.Address{}) {
|
||||||
return etherbase, nil
|
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")
|
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)
|
log.Error("Cannot start mining without etherbase", "err", err)
|
||||||
return fmt.Errorf("etherbase missing: %v", err)
|
return fmt.Errorf("etherbase missing: %v", err)
|
||||||
}
|
}
|
||||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
if _, ok := s.engine.(*clique.Clique); ok {
|
||||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
//TODO @holiman
|
||||||
if wallet == nil || err != nil {
|
log.Error("Etherbase account unavailable locally", "err", err)
|
||||||
log.Error("Etherbase account unavailable locally", "err", err)
|
return fmt.Errorf("signer not configured: %v", err)
|
||||||
return fmt.Errorf("signer missing: %v", err)
|
|
||||||
}
|
|
||||||
clique.Authorize(eb, wallet.SignHash)
|
|
||||||
}
|
}
|
||||||
// If mining is started, we can disable the transaction rejection mechanism
|
// If mining is started, we can disable the transaction rejection mechanism
|
||||||
// introduced to speed sync times.
|
// 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) IsMining() bool { return s.miner.Mining() }
|
||||||
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
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) BlockChain() *core.BlockChain { return s.blockchain }
|
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
|
||||||
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
|
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
||||||
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
func (s *Ethereum) Engine() consensus.Engine { return s.engine }
|
||||||
func (s *Ethereum) Engine() consensus.Engine { return s.engine }
|
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
||||||
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
func (s *Ethereum) IsListening() bool { return true } // Always listening
|
||||||
func (s *Ethereum) IsListening() bool { return true } // Always listening
|
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
|
||||||
func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
|
func (s *Ethereum) NetVersion() uint64 { return s.networkID }
|
||||||
func (s *Ethereum) NetVersion() uint64 { return s.networkID }
|
func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
|
||||||
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
|
// Protocols implements node.Service, returning all the currently configured
|
||||||
// network protocols to start.
|
// network protocols to start.
|
||||||
|
|
|
||||||
|
|
@ -177,53 +177,33 @@ func (s *PublicTxPoolAPI) Inspect() map[string]map[string]map[string]string {
|
||||||
return content
|
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.
|
// PrivateAccountAPI provides an API to access accounts managed by this node.
|
||||||
// It offers methods to create, (un)lock en list accounts. Some methods accept
|
// It offers methods to create, (un)lock en list accounts. Some methods accept
|
||||||
// passwords and are therefore considered private by default.
|
// passwords and are therefore considered private by default.
|
||||||
type PrivateAccountAPI struct {
|
type PrivateAccountAPI struct {
|
||||||
am *accounts.Manager
|
|
||||||
nonceLock *AddrLocker
|
nonceLock *AddrLocker
|
||||||
b Backend
|
b Backend
|
||||||
|
extapi *ExternalSignerAPI // external signer, nil if not used
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPrivateAccountAPI create a new PrivateAccountAPI.
|
// NewPrivateAccountAPI create a new PrivateAccountAPI.
|
||||||
func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
||||||
return &PrivateAccountAPI{
|
p := &PrivateAccountAPI{
|
||||||
am: b.AccountManager(),
|
|
||||||
nonceLock: nonceLock,
|
nonceLock: nonceLock,
|
||||||
b: b,
|
b: b,
|
||||||
|
extapi: b.ExternalSigner(),
|
||||||
}
|
}
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||||
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
||||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
for _, wallet := range s.am.Wallets() {
|
if s.extapi != nil {
|
||||||
for _, account := range wallet.Accounts() {
|
if accounts, err := s.extapi.listAccounts(); err == nil {
|
||||||
addresses = append(addresses, account.Address)
|
return accounts
|
||||||
}
|
}
|
||||||
|
return addresses
|
||||||
}
|
}
|
||||||
return addresses
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
@ -237,65 +217,9 @@ type rawWallet struct {
|
||||||
Accounts []accounts.Account `json:"accounts,omitempty"`
|
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.
|
// NewAccount will create a new account and returns the address for the new account.
|
||||||
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
|
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
|
||||||
acc, err := fetchKeystore(s.am).NewAccount(password)
|
return s.extapi.newAccount()
|
||||||
if err == nil {
|
|
||||||
return acc.Address, nil
|
|
||||||
}
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchKeystore retrives the encrypted keystore from the account manager.
|
// 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,
|
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
|
||||||
// encrypting it with the passphrase.
|
// encrypting it with the passphrase.
|
||||||
func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
|
||||||
key, err := crypto.HexToECDSA(privkey)
|
return common.Address{}, errors.New("Importing of raw keys is disabled when external signer is used.")
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
|
|
||||||
return acc.Address, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockAccount will unlock the account associated with the given address with
|
// 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
|
// 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.
|
// 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) {
|
func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *uint64) (bool, error) {
|
||||||
const max = uint64(time.Duration(math.MaxInt64) / time.Second)
|
return false, errors.New("Unlock is disabled when external signer is used.")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
// LockAccount will lock the account associated with the given address when it's unlocked.
|
||||||
func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
|
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
|
// signTransactions sets defaults and signs the given transaction
|
||||||
// NOTE: the caller needs to ensure that the nonceLock is held, if applicable,
|
// 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
|
// 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) {
|
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
|
// Set some sanity defaults and terminate on failure
|
||||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and sign with the wallet
|
return s.extapi.signTransaction(ctx, args)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTransaction will create a transaction from the given arguments and
|
// SendTransaction will create a transaction from the given arguments and
|
||||||
|
|
@ -416,32 +312,6 @@ func signHash(data []byte) []byte {
|
||||||
return crypto.Keccak256([]byte(msg))
|
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.
|
// 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
|
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
||||||
// the address of:
|
// 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
|
// 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.
|
// 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) {
|
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.
|
// 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 {
|
if state == nil || err != nil {
|
||||||
return nil, 0, false, err
|
return nil, 0, false, err
|
||||||
}
|
}
|
||||||
// Set sender address or use a default if none specified
|
// Set sender address
|
||||||
addr := args.From
|
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
|
// Set default gas & gas price if none were set
|
||||||
gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt()
|
gas, gasPrice := uint64(args.Gas), args.GasPrice.ToInt()
|
||||||
if gas == 0 {
|
if gas == 0 {
|
||||||
|
|
@ -943,11 +806,12 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa
|
||||||
type PublicTransactionPoolAPI struct {
|
type PublicTransactionPoolAPI struct {
|
||||||
b Backend
|
b Backend
|
||||||
nonceLock *AddrLocker
|
nonceLock *AddrLocker
|
||||||
|
extapi *ExternalSignerAPI // external signer, nil if not used
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
|
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
|
||||||
func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI {
|
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.
|
// 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
|
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.
|
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
|
||||||
type SendTxArgs struct {
|
type SendTxArgs struct {
|
||||||
From common.Address `json:"from"`
|
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
|
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
|
||||||
// transaction pool.
|
// transaction pool.
|
||||||
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
|
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
|
||||||
|
if s.extapi == nil {
|
||||||
// Look up the wallet containing the requested signer
|
return common.Hash{}, errors.New("No external signer configured")
|
||||||
account := accounts.Account{Address: args.From}
|
|
||||||
|
|
||||||
wallet, err := s.b.AccountManager().Find(account)
|
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.Nonce == nil {
|
if args.Nonce == nil {
|
||||||
// Hold the addresse's mutex around signing to prevent concurrent assignment of
|
// Hold the addresse's mutex around signing to prevent concurrent assignment of
|
||||||
// the same nonce to multiple accounts.
|
// the same nonce to multiple accounts.
|
||||||
s.nonceLock.LockAddr(args.From)
|
s.nonceLock.LockAddr(args.From)
|
||||||
defer s.nonceLock.UnlockAddr(args.From)
|
defer s.nonceLock.UnlockAddr(args.From)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set some sanity defaults and terminate on failure
|
// Set some sanity defaults and terminate on failure
|
||||||
if err := args.setDefaults(ctx, s.b); err != nil {
|
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Assemble the transaction and sign with the wallet
|
signed, err := s.extapi.signTransaction(ctx, args)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
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
|
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
|
||||||
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
// Look up the wallet containing the requested signer
|
return nil, errors.New("Sign data not implemented")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTransactionResult represents a RLP encoded signed transaction.
|
// 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 {
|
if err := args.setDefaults(ctx, s.b); err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// PendingTransactions returns the transactions that are in the transaction pool
|
||||||
// and have a from address that is one of the accounts this node manages.
|
// and have a from address that is one of the accounts this node manages.
|
||||||
func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
|
func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, error) {
|
||||||
pending, err := s.b.GetPoolTransactions()
|
return nil, errors.New("Not implemented")
|
||||||
if err != nil {
|
// TODO @holiman, make this call take a list of addresses, and show the penading transactions that are from that
|
||||||
return nil, err
|
// set of addresses
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend accepts an existing transaction and a new gas price and limit. It will remove
|
// 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 {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
signedTx *types.Transaction
|
||||||
|
)
|
||||||
for _, p := range pending {
|
for _, p := range pending {
|
||||||
var signer types.Signer = types.HomesteadSigner{}
|
var signer types.Signer = types.HomesteadSigner{}
|
||||||
if p.Protected() {
|
if p.Protected() {
|
||||||
|
|
@ -1356,7 +1166,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
|
||||||
if gasLimit != nil && *gasLimit != 0 {
|
if gasLimit != nil && *gasLimit != 0 {
|
||||||
sendArgs.Gas = gasLimit
|
sendArgs.Gas = gasLimit
|
||||||
}
|
}
|
||||||
signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction())
|
signedTx, err = s.extapi.signTransaction(ctx, sendArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -1489,3 +1299,53 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
|
||||||
func (s *PublicNetAPI) Version() string {
|
func (s *PublicNetAPI) Version() string {
|
||||||
return fmt.Sprintf("%d", s.networkVersion)
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
@ -43,8 +42,8 @@ type Backend interface {
|
||||||
SuggestPrice(ctx context.Context) (*big.Int, error)
|
SuggestPrice(ctx context.Context) (*big.Int, error)
|
||||||
ChainDb() ethdb.Database
|
ChainDb() ethdb.Database
|
||||||
EventMux() *event.TypeMux
|
EventMux() *event.TypeMux
|
||||||
AccountManager() *accounts.Manager
|
ExternalSigner() *ExternalSignerAPI
|
||||||
|
|
||||||
// BlockChain API
|
// BlockChain API
|
||||||
SetHead(number uint64)
|
SetHead(number uint64)
|
||||||
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
|
||||||
|
|
@ -103,12 +102,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
|
||||||
Namespace: "debug",
|
Namespace: "debug",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPrivateDebugAPI(apiBackend),
|
Service: NewPrivateDebugAPI(apiBackend),
|
||||||
}, {
|
},{
|
||||||
Namespace: "eth",
|
|
||||||
Version: "1.0",
|
|
||||||
Service: NewPublicAccountAPI(apiBackend.AccountManager()),
|
|
||||||
Public: true,
|
|
||||||
}, {
|
|
||||||
Namespace: "personal",
|
Namespace: "personal",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: NewPrivateAccountAPI(apiBackend, nonceLock),
|
Service: NewPrivateAccountAPI(apiBackend, nonceLock),
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
|
@ -36,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LesApiBackend struct {
|
type LesApiBackend struct {
|
||||||
|
|
@ -183,8 +183,8 @@ func (b *LesApiBackend) EventMux() *event.TypeMux {
|
||||||
return b.eth.eventMux
|
return b.eth.eventMux
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LesApiBackend) AccountManager() *accounts.Manager {
|
func (b *LesApiBackend) ExternalSigner() *ethapi.ExternalSignerAPI{
|
||||||
return b.eth.accountManager
|
return b.eth.externalSigner
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
|
func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -69,7 +68,7 @@ type LightEthereum struct {
|
||||||
|
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
engine consensus.Engine
|
engine consensus.Engine
|
||||||
accountManager *accounts.Manager
|
externalSigner *ethapi.ExternalSignerAPI
|
||||||
|
|
||||||
networkId uint64
|
networkId uint64
|
||||||
netRPCService *ethapi.PublicNetAPI
|
netRPCService *ethapi.PublicNetAPI
|
||||||
|
|
@ -101,7 +100,6 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
eventMux: ctx.EventMux,
|
eventMux: ctx.EventMux,
|
||||||
peers: peers,
|
peers: peers,
|
||||||
reqDist: newRequestDistributor(peers, quitSync),
|
reqDist: newRequestDistributor(peers, quitSync),
|
||||||
accountManager: ctx.AccountManager,
|
|
||||||
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
|
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb),
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
networkId: config.NetworkId,
|
networkId: config.NetworkId,
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@ type Config struct {
|
||||||
// NoUSB disables hardware wallet monitoring and connectivity.
|
// NoUSB disables hardware wallet monitoring and connectivity.
|
||||||
NoUSB bool `toml:",omitempty"`
|
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
|
// 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
|
// 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
|
// pipe path on Windows), whereas if it's a resolvable path name (absolute or
|
||||||
|
|
|
||||||
35
node/node.go
35
node/node.go
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
|
@ -34,15 +33,15 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/prometheus/prometheus/util/flock"
|
"github.com/prometheus/prometheus/util/flock"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Node is a container on which services can be registered.
|
// Node is a container on which services can be registered.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
|
||||||
config *Config
|
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
|
instanceDirLock flock.Releaser // prevents concurrent use of instance directory
|
||||||
|
|
||||||
serverConfig p2p.Config
|
serverConfig p2p.Config
|
||||||
|
|
@ -97,20 +96,12 @@ func New(conf *Config) (*Node, error) {
|
||||||
if strings.HasSuffix(conf.Name, ".ipc") {
|
if strings.HasSuffix(conf.Name, ".ipc") {
|
||||||
return nil, errors.New(`Config.Name cannot end in ".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 {
|
if conf.Logger == nil {
|
||||||
conf.Logger = log.New()
|
conf.Logger = log.New()
|
||||||
}
|
}
|
||||||
// Note: any interaction with Config that would create/touch files
|
// Note: any interaction with Config that would create/touch files
|
||||||
// in the data directory or instance directory is delayed until Start.
|
// in the data directory or instance directory is delayed until Start.
|
||||||
return &Node{
|
return &Node{
|
||||||
accman: am,
|
|
||||||
ephemeralKeystore: ephemeralKeystore,
|
|
||||||
config: conf,
|
config: conf,
|
||||||
serviceFuncs: []ServiceConstructor{},
|
serviceFuncs: []ServiceConstructor{},
|
||||||
ipcEndpoint: conf.IPCEndpoint(),
|
ipcEndpoint: conf.IPCEndpoint(),
|
||||||
|
|
@ -173,7 +164,6 @@ func (n *Node) Start() error {
|
||||||
config: n.config,
|
config: n.config,
|
||||||
services: make(map[reflect.Type]Service),
|
services: make(map[reflect.Type]Service),
|
||||||
EventMux: n.eventmux,
|
EventMux: n.eventmux,
|
||||||
AccountManager: n.accman,
|
|
||||||
}
|
}
|
||||||
for kind, s := range services { // copy needed for threaded access
|
for kind, s := range services { // copy needed for threaded access
|
||||||
ctx.services[kind] = s
|
ctx.services[kind] = s
|
||||||
|
|
@ -435,18 +425,9 @@ func (n *Node) Stop() error {
|
||||||
// unblock n.Wait
|
// unblock n.Wait
|
||||||
close(n.stop)
|
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 {
|
if len(failure.Services) > 0 {
|
||||||
return failure
|
return failure
|
||||||
}
|
}
|
||||||
if keystoreErr != nil {
|
|
||||||
return keystoreErr
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -537,11 +518,6 @@ func (n *Node) InstanceDir() string {
|
||||||
return n.config.instanceDir()
|
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.
|
// IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
|
||||||
func (n *Node) IPCEndpoint() string {
|
func (n *Node) IPCEndpoint() string {
|
||||||
return n.ipcEndpoint
|
return n.ipcEndpoint
|
||||||
|
|
@ -578,6 +554,13 @@ func (n *Node) ResolvePath(x string) string {
|
||||||
return n.config.ResolvePath(x)
|
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.
|
// apis returns the collection of RPC descriptors this node offers.
|
||||||
func (n *Node) apis() []rpc.API {
|
func (n *Node) apis() []rpc.API {
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package node
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
@ -33,7 +32,6 @@ type ServiceContext struct {
|
||||||
config *Config
|
config *Config
|
||||||
services map[reflect.Type]Service // Index of the already constructed services
|
services map[reflect.Type]Service // Index of the already constructed services
|
||||||
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
|
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
|
// OpenDatabase opens an existing database with the given name (or creates one
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue