Merge pull request #1283 from ethersphere/frontier/accounts
Account management improvements
This commit is contained in:
commit
9bb575be7d
@ -26,7 +26,7 @@ This abstracts part of a user's interaction with an account she controls.
|
|||||||
It's not an abstraction of core Ethereum accounts data type / logic -
|
It's not an abstraction of core Ethereum accounts data type / logic -
|
||||||
for that see the core processing code of blocks / txs.
|
for that see the core processing code of blocks / txs.
|
||||||
|
|
||||||
Currently this is pretty much a passthrough to the KeyStore2 interface,
|
Currently this is pretty much a passthrough to the KeyStore interface,
|
||||||
and accounts persistence is derived from stored keys' addresses
|
and accounts persistence is derived from stored keys' addresses
|
||||||
|
|
||||||
*/
|
*/
|
||||||
@ -36,6 +36,7 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -49,17 +50,12 @@ var (
|
|||||||
ErrNoKeys = errors.New("no keys in store")
|
ErrNoKeys = errors.New("no keys in store")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// Default unlock duration (in seconds) when an account is unlocked from the console
|
|
||||||
DefaultAccountUnlockDuration = 300
|
|
||||||
)
|
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
Address common.Address
|
Address common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
keyStore crypto.KeyStore2
|
keyStore crypto.KeyStore
|
||||||
unlocked map[common.Address]*unlocked
|
unlocked map[common.Address]*unlocked
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -69,7 +65,7 @@ type unlocked struct {
|
|||||||
abort chan struct{}
|
abort chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(keyStore crypto.KeyStore2) *Manager {
|
func NewManager(keyStore crypto.KeyStore) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
keyStore: keyStore,
|
keyStore: keyStore,
|
||||||
unlocked: make(map[common.Address]*unlocked),
|
unlocked: make(map[common.Address]*unlocked),
|
||||||
@ -86,19 +82,6 @@ func (am *Manager) HasAccount(addr common.Address) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) Primary() (addr common.Address, err error) {
|
|
||||||
addrs, err := am.keyStore.GetKeyAddresses()
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return common.Address{}, ErrNoKeys
|
|
||||||
} else if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
if len(addrs) == 0 {
|
|
||||||
return common.Address{}, ErrNoKeys
|
|
||||||
}
|
|
||||||
return addrs[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *Manager) DeleteAccount(address common.Address, auth string) error {
|
func (am *Manager) DeleteAccount(address common.Address, auth string) error {
|
||||||
return am.keyStore.DeleteKey(address, auth)
|
return am.keyStore.DeleteKey(address, auth)
|
||||||
}
|
}
|
||||||
@ -114,72 +97,41 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error)
|
|||||||
return signature, err
|
return signature, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimedUnlock unlocks the account with the given address.
|
// unlock indefinitely
|
||||||
// When timeout has passed, the account will be locked again.
|
func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
|
||||||
|
return am.TimedUnlock(addr, keyAuth, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock unlocks the account with the given address. The account
|
||||||
|
// stays unlocked for the duration of timeout
|
||||||
|
// it timeout is 0 the account is unlocked for the entire session
|
||||||
func (am *Manager) TimedUnlock(addr common.Address, keyAuth string, timeout time.Duration) error {
|
func (am *Manager) TimedUnlock(addr common.Address, keyAuth string, timeout time.Duration) error {
|
||||||
key, err := am.keyStore.GetKey(addr, keyAuth)
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u := am.addUnlocked(addr, key)
|
var u *unlocked
|
||||||
go am.dropLater(addr, u, timeout)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock unlocks the account with the given address. The account
|
|
||||||
// stays unlocked until the program exits or until a TimedUnlock
|
|
||||||
// timeout (started after the call to Unlock) expires.
|
|
||||||
func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
|
|
||||||
key, err := am.keyStore.GetKey(addr, keyAuth)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
am.addUnlocked(addr, key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *Manager) NewAccount(auth string) (Account, error) {
|
|
||||||
key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
|
|
||||||
if err != nil {
|
|
||||||
return Account{}, err
|
|
||||||
}
|
|
||||||
return Account{Address: key.Address}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *Manager) Accounts() ([]Account, error) {
|
|
||||||
addresses, err := am.keyStore.GetKeyAddresses()
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, ErrNoKeys
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
accounts := make([]Account, len(addresses))
|
|
||||||
for i, addr := range addresses {
|
|
||||||
accounts[i] = Account{
|
|
||||||
Address: addr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accounts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *Manager) addUnlocked(addr common.Address, key *crypto.Key) *unlocked {
|
|
||||||
u := &unlocked{Key: key, abort: make(chan struct{})}
|
|
||||||
am.mutex.Lock()
|
am.mutex.Lock()
|
||||||
prev, found := am.unlocked[addr]
|
defer am.mutex.Unlock()
|
||||||
|
var found bool
|
||||||
|
u, found = am.unlocked[addr]
|
||||||
if found {
|
if found {
|
||||||
// terminate dropLater for this key to avoid unexpected drops.
|
// terminate dropLater for this key to avoid unexpected drops.
|
||||||
close(prev.abort)
|
if u.abort != nil {
|
||||||
// the key is zeroed here instead of in dropLater because
|
close(u.abort)
|
||||||
// there might not actually be a dropLater running for this
|
}
|
||||||
// key, i.e. when Unlock was used.
|
}
|
||||||
zeroKey(prev.PrivateKey)
|
if timeout > 0 {
|
||||||
|
u = &unlocked{Key: key, abort: make(chan struct{})}
|
||||||
|
go am.expire(addr, u, timeout)
|
||||||
|
} else {
|
||||||
|
u = &unlocked{Key: key}
|
||||||
}
|
}
|
||||||
am.unlocked[addr] = u
|
am.unlocked[addr] = u
|
||||||
am.mutex.Unlock()
|
return nil
|
||||||
return u
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) dropLater(addr common.Address, u *unlocked, timeout time.Duration) {
|
func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duration) {
|
||||||
t := time.NewTimer(timeout)
|
t := time.NewTimer(timeout)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
select {
|
select {
|
||||||
@ -199,6 +151,44 @@ func (am *Manager) dropLater(addr common.Address, u *unlocked, timeout time.Dura
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *Manager) NewAccount(auth string) (Account, error) {
|
||||||
|
key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
|
||||||
|
if err != nil {
|
||||||
|
return Account{}, err
|
||||||
|
}
|
||||||
|
return Account{Address: key.Address}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *Manager) AddressByIndex(index int) (addr string, err error) {
|
||||||
|
var addrs []common.Address
|
||||||
|
addrs, err = am.keyStore.GetKeyAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index < 0 || index >= len(addrs) {
|
||||||
|
err = fmt.Errorf("index out of range: %d (should be 0-%d)", index, len(addrs)-1)
|
||||||
|
} else {
|
||||||
|
addr = addrs[index].Hex()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *Manager) Accounts() ([]Account, error) {
|
||||||
|
addresses, err := am.keyStore.GetKeyAddresses()
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrNoKeys
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
accounts := make([]Account, len(addresses))
|
||||||
|
for i, addr := range addresses {
|
||||||
|
accounts[i] = Account{
|
||||||
|
Address: addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
// zeroKey zeroes a private key in memory.
|
// zeroKey zeroes a private key in memory.
|
||||||
func zeroKey(k *ecdsa.PrivateKey) {
|
func zeroKey(k *ecdsa.PrivateKey) {
|
||||||
b := k.D.Bits()
|
b := k.D.Bits()
|
||||||
@ -229,6 +219,19 @@ func (am *Manager) Import(path string, keyAuth string) (Account, error) {
|
|||||||
return Account{Address: key.Address}, nil
|
return Account{Address: key.Address}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *Manager) Update(addr common.Address, authFrom, authTo string) (err error) {
|
||||||
|
var key *crypto.Key
|
||||||
|
key, err = am.keyStore.GetKey(addr, authFrom)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = am.keyStore.StoreKey(key, authTo)
|
||||||
|
if err == nil {
|
||||||
|
am.keyStore.Cleanup(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) {
|
func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) {
|
||||||
var key *crypto.Key
|
var key *crypto.Key
|
||||||
key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password)
|
key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password)
|
||||||
|
@ -58,9 +58,51 @@ func TestTimedUnlock(t *testing.T) {
|
|||||||
if err != ErrLocked {
|
if err != ErrLocked {
|
||||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore2) (string, crypto.KeyStore2) {
|
func TestOverrideUnlock(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, crypto.NewKeyStorePassphrase)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
am := NewManager(ks)
|
||||||
|
pass := "foo"
|
||||||
|
a1, err := am.NewAccount(pass)
|
||||||
|
toSign := randentropy.GetEntropyCSPRNG(32)
|
||||||
|
|
||||||
|
// Unlock indefinitely
|
||||||
|
if err = am.Unlock(a1.Address, pass); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase works because account is temp unlocked
|
||||||
|
_, err = am.Sign(a1, toSign)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset unlock to a shorter period, invalidates the previous unlock
|
||||||
|
if err = am.TimedUnlock(a1.Address, pass, 100*time.Millisecond); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase still works because account is temp unlocked
|
||||||
|
_, err = am.Sign(a1, toSign)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing fails again after automatic locking
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
_, err = am.Sign(a1, toSign)
|
||||||
|
if err != ErrLocked {
|
||||||
|
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore) (string, crypto.KeyStore) {
|
||||||
d, err := ioutil.TempDir("", "eth-keystore-test")
|
d, err := ioutil.TempDir("", "eth-keystore-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -20,8 +20,8 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/rpc/comms"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc/comms"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -127,6 +127,7 @@ func TestNodeInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer ethereum.Stop()
|
defer ethereum.Stop()
|
||||||
defer os.RemoveAll(tmp)
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
|
want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
|
||||||
checkEvalJSON(t, repl, `admin.nodeInfo`, want)
|
checkEvalJSON(t, repl, `admin.nodeInfo`, want)
|
||||||
}
|
}
|
||||||
@ -140,8 +141,7 @@ func TestAccounts(t *testing.T) {
|
|||||||
defer os.RemoveAll(tmp)
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
|
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
|
||||||
checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`)
|
checkEvalJSON(t, repl, `eth.coinbase`, `null`)
|
||||||
|
|
||||||
val, err := repl.re.Run(`personal.newAccount("password")`)
|
val, err := repl.re.Run(`personal.newAccount("password")`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
@ -151,9 +151,7 @@ func TestAccounts(t *testing.T) {
|
|||||||
t.Errorf("address not hex: %q", addr)
|
t.Errorf("address not hex: %q", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip until order fixed #824
|
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`)
|
||||||
// checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`", "`+addr+`"]`)
|
|
||||||
// checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockChain(t *testing.T) {
|
func TestBlockChain(t *testing.T) {
|
||||||
|
104
cmd/geth/main.go
104
cmd/geth/main.go
@ -153,9 +153,12 @@ Note that exporting your key in unencrypted format is NOT supported.
|
|||||||
|
|
||||||
Keys are stored under <DATADIR>/keys.
|
Keys are stored under <DATADIR>/keys.
|
||||||
It is safe to transfer the entire directory or the individual keys therein
|
It is safe to transfer the entire directory or the individual keys therein
|
||||||
between ethereum nodes.
|
between ethereum nodes by simply copying.
|
||||||
Make sure you backup your keys regularly.
|
Make sure you backup your keys regularly.
|
||||||
|
|
||||||
|
In order to use your account to send transactions, you need to unlock them using the
|
||||||
|
'--unlock' option. The argument is a comma
|
||||||
|
|
||||||
And finally. DO NOT FORGET YOUR PASSWORD.
|
And finally. DO NOT FORGET YOUR PASSWORD.
|
||||||
`,
|
`,
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
@ -186,6 +189,33 @@ 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.
|
password to file or expose in any other way.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Action: accountUpdate,
|
||||||
|
Name: "update",
|
||||||
|
Usage: "update an existing account",
|
||||||
|
Description: `
|
||||||
|
|
||||||
|
ethereum account update <address>
|
||||||
|
|
||||||
|
Update an existing account.
|
||||||
|
|
||||||
|
The account is saved in the newest version in encrypted format, you are prompted
|
||||||
|
for a passphrase to unlock the account and another to save the updated file.
|
||||||
|
|
||||||
|
This same command can therefore be used to migrate an account of a deprecated
|
||||||
|
format to the newest format or change the password for an account.
|
||||||
|
|
||||||
|
For non-interactive use the passphrase can be specified with the --password flag:
|
||||||
|
|
||||||
|
ethereum --password <passwordfile> account new
|
||||||
|
|
||||||
|
Since only one password can be given, only format update can be performed,
|
||||||
|
changing your password is only possible interactively.
|
||||||
|
|
||||||
|
Note that account update has the a side effect that the order of your accounts
|
||||||
|
changes.
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Action: accountImport,
|
Action: accountImport,
|
||||||
Name: "import",
|
Name: "import",
|
||||||
@ -430,19 +460,30 @@ func execJSFiles(ctx *cli.Context) {
|
|||||||
ethereum.WaitForShutdown()
|
ethereum.WaitForShutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (passphrase string) {
|
func unlockAccount(ctx *cli.Context, am *accounts.Manager, addr string, i int) (addrHex, auth string) {
|
||||||
var err error
|
var err error
|
||||||
// Load startup keys. XXX we are going to need a different format
|
// Load startup keys. XXX we are going to need a different format
|
||||||
|
|
||||||
if !((len(account) == 40) || (len(account) == 42)) { // with or without 0x
|
if !((len(addr) == 40) || (len(addr) == 42)) { // with or without 0x
|
||||||
utils.Fatalf("Invalid account address '%s'", account)
|
var index int
|
||||||
|
index, err = strconv.Atoi(addr)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Invalid account address '%s'", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrHex, err = am.AddressByIndex(index)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addrHex = addr
|
||||||
}
|
}
|
||||||
// Attempt to unlock the account 3 times
|
// Attempt to unlock the account 3 times
|
||||||
attempts := 3
|
attempts := 3
|
||||||
for tries := 0; tries < attempts; tries++ {
|
for tries := 0; tries < attempts; tries++ {
|
||||||
msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", account, tries+1, attempts)
|
msg := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", addr, tries+1, attempts)
|
||||||
passphrase = getPassPhrase(ctx, msg, false)
|
auth = getPassPhrase(ctx, msg, false, i)
|
||||||
err = am.Unlock(common.HexToAddress(account), passphrase)
|
err = am.Unlock(common.HexToAddress(addrHex), auth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -450,7 +491,7 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Unlock account failed '%v'", err)
|
utils.Fatalf("Unlock account failed '%v'", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Account '%s' unlocked.\n", account)
|
fmt.Printf("Account '%s' unlocked.\n", addr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,16 +533,12 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
|
|||||||
|
|
||||||
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
|
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
|
||||||
accounts := strings.Split(account, " ")
|
accounts := strings.Split(account, " ")
|
||||||
for _, account := range accounts {
|
for i, account := range accounts {
|
||||||
if len(account) > 0 {
|
if len(account) > 0 {
|
||||||
if account == "primary" {
|
if account == "primary" {
|
||||||
primaryAcc, err := am.Primary()
|
utils.Fatalf("the 'primary' keyword is deprecated. You can use integer indexes, but the indexes are not permanent, they can change if you add external keys, export your keys or copy your keystore to another node.")
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("no primary account: %v", err)
|
|
||||||
}
|
}
|
||||||
account = primaryAcc.Hex()
|
unlockAccount(ctx, am, account, i)
|
||||||
}
|
|
||||||
unlockAccount(ctx, am, account)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Start auxiliary services if enabled.
|
// Start auxiliary services if enabled.
|
||||||
@ -528,14 +565,12 @@ func accountList(ctx *cli.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not list accounts: %v", err)
|
utils.Fatalf("Could not list accounts: %v", err)
|
||||||
}
|
}
|
||||||
name := "Primary"
|
|
||||||
for i, acct := range accts {
|
for i, acct := range accts {
|
||||||
fmt.Printf("%s #%d: %x\n", name, i, acct)
|
fmt.Printf("Account #%d: %x\n", i, acct)
|
||||||
name = "Account"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (passphrase string) {
|
func getPassPhrase(ctx *cli.Context, desc string, confirmation bool, i int) (passphrase string) {
|
||||||
passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
|
passfile := ctx.GlobalString(utils.PasswordFileFlag.Name)
|
||||||
if len(passfile) == 0 {
|
if len(passfile) == 0 {
|
||||||
fmt.Println(desc)
|
fmt.Println(desc)
|
||||||
@ -559,14 +594,22 @@ func getPassPhrase(ctx *cli.Context, desc string, confirmation bool) (passphrase
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
|
utils.Fatalf("Unable to read password file '%s': %v", passfile, err)
|
||||||
}
|
}
|
||||||
passphrase = string(passbytes)
|
// this is backwards compatible if the same password unlocks several accounts
|
||||||
|
// it also has the consequence that trailing newlines will not count as part
|
||||||
|
// of the password, so --password <(echo -n 'pass') will now work without -n
|
||||||
|
passphrases := strings.Split(string(passbytes), "\n")
|
||||||
|
if i >= len(passphrases) {
|
||||||
|
passphrase = passphrases[len(passphrases)-1]
|
||||||
|
} else {
|
||||||
|
passphrase = passphrases[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountCreate(ctx *cli.Context) {
|
func accountCreate(ctx *cli.Context) {
|
||||||
am := utils.MakeAccountManager(ctx)
|
am := utils.MakeAccountManager(ctx)
|
||||||
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true)
|
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0)
|
||||||
acct, err := am.NewAccount(passphrase)
|
acct, err := am.NewAccount(passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not create the account: %v", err)
|
utils.Fatalf("Could not create the account: %v", err)
|
||||||
@ -574,6 +617,21 @@ func accountCreate(ctx *cli.Context) {
|
|||||||
fmt.Printf("Address: %x\n", acct)
|
fmt.Printf("Address: %x\n", acct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func accountUpdate(ctx *cli.Context) {
|
||||||
|
am := utils.MakeAccountManager(ctx)
|
||||||
|
arg := ctx.Args().First()
|
||||||
|
if len(arg) == 0 {
|
||||||
|
utils.Fatalf("account address or index must be given as argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, authFrom := unlockAccount(ctx, am, arg, 0)
|
||||||
|
authTo := getPassPhrase(ctx, "Please give a new password. Do not forget this password.", true, 0)
|
||||||
|
err := am.Update(common.HexToAddress(addr), authFrom, authTo)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Could not update the account: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func importWallet(ctx *cli.Context) {
|
func importWallet(ctx *cli.Context) {
|
||||||
keyfile := ctx.Args().First()
|
keyfile := ctx.Args().First()
|
||||||
if len(keyfile) == 0 {
|
if len(keyfile) == 0 {
|
||||||
@ -585,7 +643,7 @@ func importWallet(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
am := utils.MakeAccountManager(ctx)
|
am := utils.MakeAccountManager(ctx)
|
||||||
passphrase := getPassPhrase(ctx, "", false)
|
passphrase := getPassPhrase(ctx, "", false, 0)
|
||||||
|
|
||||||
acct, err := am.ImportPreSaleKey(keyJson, passphrase)
|
acct, err := am.ImportPreSaleKey(keyJson, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -600,7 +658,7 @@ func accountImport(ctx *cli.Context) {
|
|||||||
utils.Fatalf("keyfile must be given as argument")
|
utils.Fatalf("keyfile must be given as argument")
|
||||||
}
|
}
|
||||||
am := utils.MakeAccountManager(ctx)
|
am := utils.MakeAccountManager(ctx)
|
||||||
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true)
|
passphrase := getPassPhrase(ctx, "Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0)
|
||||||
acct, err := am.Import(keyfile, passphrase)
|
acct, err := am.Import(keyfile, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not create the account: %v", err)
|
utils.Fatalf("Could not create the account: %v", err)
|
||||||
|
@ -133,7 +133,7 @@ var (
|
|||||||
|
|
||||||
UnlockedAccountFlag = cli.StringFlag{
|
UnlockedAccountFlag = cli.StringFlag{
|
||||||
Name: "unlock",
|
Name: "unlock",
|
||||||
Usage: "Unlock the account given until this program exits (prompts for password). '--unlock primary' unlocks the primary account",
|
Usage: "Unlock the account given until this program exits (prompts for password). '--unlock n' unlocks the n-th account in order or creation.",
|
||||||
Value: "",
|
Value: "",
|
||||||
}
|
}
|
||||||
PasswordFileFlag = cli.StringFlag{
|
PasswordFileFlag = cli.StringFlag{
|
||||||
|
@ -209,7 +209,7 @@ func ImportBlockTestKey(privKeyBytes []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
|
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
|
||||||
func ImportPreSaleKey(keyStore KeyStore2, keyJSON []byte, password string) (*Key, error) {
|
func ImportPreSaleKey(keyStore KeyStore, keyJSON []byte, password string) (*Key, error) {
|
||||||
key, err := decryptPreSaleKey(keyJSON, password)
|
key, err := decryptPreSaleKey(keyJSON, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -41,8 +41,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
"code.google.com/p/go-uuid/uuid"
|
||||||
@ -65,7 +63,7 @@ type keyStorePassphrase struct {
|
|||||||
keysDirPath string
|
keysDirPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeyStorePassphrase(path string) KeyStore2 {
|
func NewKeyStorePassphrase(path string) KeyStore {
|
||||||
return &keyStorePassphrase{path}
|
return &keyStorePassphrase{path}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,20 +72,23 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
||||||
keyBytes, keyId, err := DecryptKeyFromFile(ks, keyAddr, auth)
|
keyBytes, keyId, err := decryptKeyFromFile(ks.keysDirPath, keyAddr, auth)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
key = &Key{
|
key = &Key{
|
||||||
Id: uuid.UUID(keyId),
|
Id: uuid.UUID(keyId),
|
||||||
Address: keyAddr,
|
Address: keyAddr,
|
||||||
PrivateKey: ToECDSA(keyBytes),
|
PrivateKey: ToECDSA(keyBytes),
|
||||||
}
|
}
|
||||||
return key, err
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks keyStorePassphrase) Cleanup(keyAddr common.Address) (err error) {
|
||||||
|
return cleanup(ks.keysDirPath, keyAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
|
func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
|
||||||
return GetKeyAddresses(ks.keysDirPath)
|
return getKeyAddresses(ks.keysDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
||||||
@ -139,42 +140,40 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
return writeKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
||||||
// only delete if correct passphrase is given
|
// only delete if correct passphrase is given
|
||||||
_, _, err = DecryptKeyFromFile(ks, keyAddr, auth)
|
_, _, err = decryptKeyFromFile(ks.keysDirPath, keyAddr, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr[:]))
|
return deleteKey(ks.keysDirPath, keyAddr)
|
||||||
return os.RemoveAll(keyDirPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptKeyFromFile(ks keyStorePassphrase, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func decryptKeyFromFile(keysDirPath string, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||||
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
fmt.Printf("%v\n", keyAddr.Hex())
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := make(map[string]interface{})
|
m := make(map[string]interface{})
|
||||||
err = json.Unmarshal(fileContent, &m)
|
err = getKey(keysDirPath, keyAddr, &m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
v := reflect.ValueOf(m["version"])
|
v := reflect.ValueOf(m["version"])
|
||||||
if v.Kind() == reflect.String && v.String() == "1" {
|
if v.Kind() == reflect.String && v.String() == "1" {
|
||||||
k := new(encryptedKeyJSONV1)
|
k := new(encryptedKeyJSONV1)
|
||||||
err := json.Unmarshal(fileContent, k)
|
err = getKey(keysDirPath, keyAddr, &k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return
|
||||||
}
|
}
|
||||||
return decryptKeyV1(k, auth)
|
return decryptKeyV1(k, auth)
|
||||||
} else {
|
} else {
|
||||||
k := new(encryptedKeyJSONV3)
|
k := new(encryptedKeyJSONV3)
|
||||||
err := json.Unmarshal(fileContent, k)
|
err = getKey(keysDirPath, keyAddr, &k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return
|
||||||
}
|
}
|
||||||
return decryptKeyV3(k, auth)
|
return decryptKeyV3(k, auth)
|
||||||
}
|
}
|
||||||
|
@ -27,28 +27,30 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: rename to KeyStore when replacing existing KeyStore
|
type KeyStore interface {
|
||||||
type KeyStore2 interface {
|
|
||||||
// create new key using io.Reader entropy source and optionally using auth string
|
// create new key using io.Reader entropy source and optionally using auth string
|
||||||
GenerateNewKey(io.Reader, string) (*Key, error)
|
GenerateNewKey(io.Reader, string) (*Key, error)
|
||||||
GetKey(common.Address, string) (*Key, error) // key from addr and auth string
|
GetKey(common.Address, string) (*Key, error) // get key from addr and auth string
|
||||||
GetKeyAddresses() ([]common.Address, error) // get all addresses
|
GetKeyAddresses() ([]common.Address, error) // get all addresses
|
||||||
StoreKey(*Key, string) error // store key optionally using auth string
|
StoreKey(*Key, string) error // store key optionally using auth string
|
||||||
DeleteKey(common.Address, string) error // delete key by addr and auth string
|
DeleteKey(common.Address, string) error // delete key by addr and auth string
|
||||||
|
Cleanup(keyAddr common.Address) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyStorePlain struct {
|
type keyStorePlain struct {
|
||||||
keysDirPath string
|
keysDirPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeyStorePlain(path string) KeyStore2 {
|
func NewKeyStorePlain(path string) KeyStore {
|
||||||
return &keyStorePlain{path}
|
return &keyStorePlain{path}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, e
|
|||||||
return GenerateNewKeyDefault(ks, rand, auth)
|
return GenerateNewKeyDefault(ks, rand, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key, err error) {
|
func GenerateNewKeyDefault(ks KeyStore, rand io.Reader, auth string) (key *Key, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("GenerateNewKey error: %v", r)
|
err = fmt.Errorf("GenerateNewKey error: %v", r)
|
||||||
@ -68,62 +70,149 @@ func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
||||||
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key = new(Key)
|
key = new(Key)
|
||||||
err = json.Unmarshal(fileContent, key)
|
err = getKey(ks.keysDirPath, keyAddr, key)
|
||||||
return key, err
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKey(keysDirPath string, keyAddr common.Address, content interface{}) (err error) {
|
||||||
|
fileContent, err := getKeyFile(keysDirPath, keyAddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return json.Unmarshal(fileContent, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
|
func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
|
||||||
return GetKeyAddresses(ks.keysDirPath)
|
return getKeyAddresses(ks.keysDirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks keyStorePlain) Cleanup(keyAddr common.Address) (err error) {
|
||||||
|
return cleanup(ks.keysDirPath, keyAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
|
func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
|
||||||
keyJSON, err := json.Marshal(key)
|
keyJSON, err := json.Marshal(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
err = WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
err = writeKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
||||||
keyDirPath := filepath.Join(ks.keysDirPath, keyAddr.Hex())
|
return deleteKey(ks.keysDirPath, keyAddr)
|
||||||
err = os.RemoveAll(keyDirPath)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
|
func deleteKey(keysDirPath string, keyAddr common.Address) (err error) {
|
||||||
fileName := hex.EncodeToString(keyAddr[:])
|
var path string
|
||||||
return ioutil.ReadFile(filepath.Join(keysDirPath, fileName, fileName))
|
path, err = getKeyFilePath(keysDirPath, keyAddr)
|
||||||
|
if err == nil {
|
||||||
|
addrHex := hex.EncodeToString(keyAddr[:])
|
||||||
|
if path == filepath.Join(keysDirPath, addrHex, addrHex) {
|
||||||
|
path = filepath.Join(keysDirPath, addrHex)
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(path)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteKeyFile(addr common.Address, keysDirPath string, content []byte) (err error) {
|
func getKeyFilePath(keysDirPath string, keyAddr common.Address) (keyFilePath string, err error) {
|
||||||
addrHex := hex.EncodeToString(addr[:])
|
addrHex := hex.EncodeToString(keyAddr[:])
|
||||||
keyDirPath := filepath.Join(keysDirPath, addrHex)
|
matches, err := filepath.Glob(filepath.Join(keysDirPath, fmt.Sprintf("*--%s", addrHex)))
|
||||||
keyFilePath := filepath.Join(keyDirPath, addrHex)
|
if len(matches) > 0 {
|
||||||
err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
|
if err == nil {
|
||||||
|
keyFilePath = matches[len(matches)-1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyFilePath = filepath.Join(keysDirPath, addrHex, addrHex)
|
||||||
|
_, err = os.Stat(keyFilePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanup(keysDirPath string, keyAddr common.Address) (err error) {
|
||||||
|
fileInfos, err := ioutil.ReadDir(keysDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var paths []string
|
||||||
|
account := hex.EncodeToString(keyAddr[:])
|
||||||
|
for _, fileInfo := range fileInfos {
|
||||||
|
path := filepath.Join(keysDirPath, fileInfo.Name())
|
||||||
|
if len(path) >= 40 {
|
||||||
|
addr := path[len(path)-40 : len(path)]
|
||||||
|
if addr == account {
|
||||||
|
if path == filepath.Join(keysDirPath, addr, addr) {
|
||||||
|
path = filepath.Join(keysDirPath, addr)
|
||||||
|
}
|
||||||
|
paths = append(paths, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(paths) > 1 {
|
||||||
|
for i := 0; err == nil && i < len(paths)-1; i++ {
|
||||||
|
err = os.RemoveAll(paths[i])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
|
||||||
|
var keyFilePath string
|
||||||
|
keyFilePath, err = getKeyFilePath(keysDirPath, keyAddr)
|
||||||
|
if err == nil {
|
||||||
|
fileContent, err = ioutil.ReadFile(keyFilePath)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeKeyFile(addr common.Address, keysDirPath string, content []byte) (err error) {
|
||||||
|
filename := keyFileName(addr)
|
||||||
|
// read, write and dir search for user
|
||||||
|
err = os.MkdirAll(keysDirPath, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
|
// read, write for user
|
||||||
|
return ioutil.WriteFile(filepath.Join(keysDirPath, filename), content, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetKeyAddresses(keysDirPath string) (addresses []common.Address, err error) {
|
// keyFilePath implements the naming convention for keyfiles:
|
||||||
|
// UTC--<created_at UTC ISO8601>-<address hex>
|
||||||
|
func keyFileName(keyAddr common.Address) string {
|
||||||
|
ts := time.Now().UTC()
|
||||||
|
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toISO8601(t time.Time) string {
|
||||||
|
var tz string
|
||||||
|
name, offset := t.Zone()
|
||||||
|
if name == "UTC" {
|
||||||
|
tz = "Z"
|
||||||
|
} else {
|
||||||
|
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyAddresses(keysDirPath string) (addresses []common.Address, err error) {
|
||||||
fileInfos, err := ioutil.ReadDir(keysDirPath)
|
fileInfos, err := ioutil.ReadDir(keysDirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, fileInfo := range fileInfos {
|
for _, fileInfo := range fileInfos {
|
||||||
address, err := hex.DecodeString(fileInfo.Name())
|
filename := fileInfo.Name()
|
||||||
if err != nil {
|
if len(filename) >= 40 {
|
||||||
continue
|
addr := filename[len(filename)-40 : len(filename)]
|
||||||
}
|
address, err := hex.DecodeString(addr)
|
||||||
|
if err == nil {
|
||||||
addresses = append(addresses, common.BytesToAddress(address))
|
addresses = append(addresses, common.BytesToAddress(address))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return addresses, err
|
return addresses, err
|
||||||
}
|
}
|
||||||
|
@ -464,17 +464,9 @@ func (s *Ethereum) StartMining(threads int) error {
|
|||||||
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
||||||
eb = s.etherbase
|
eb = s.etherbase
|
||||||
if (eb == common.Address{}) {
|
if (eb == common.Address{}) {
|
||||||
primary, err := s.accountManager.Primary()
|
err = fmt.Errorf("etherbase address must be explicitly specified")
|
||||||
if err != nil {
|
|
||||||
return eb, err
|
|
||||||
}
|
}
|
||||||
if (primary == common.Address{}) {
|
return
|
||||||
err = fmt.Errorf("no accounts found")
|
|
||||||
return eb, err
|
|
||||||
}
|
|
||||||
eb = primary
|
|
||||||
}
|
|
||||||
return eb, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Ethereum) StopMining() { s.miner.Stop() }
|
func (s *Ethereum) StopMining() { s.miner.Stop() }
|
||||||
|
@ -467,7 +467,10 @@ func (self *XEth) IsListening() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *XEth) Coinbase() string {
|
func (self *XEth) Coinbase() string {
|
||||||
eb, _ := self.backend.Etherbase()
|
eb, err := self.backend.Etherbase()
|
||||||
|
if err != nil {
|
||||||
|
return "0x0"
|
||||||
|
}
|
||||||
return eb.Hex()
|
return eb.Hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user