aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/account_manager.go
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2016-03-03 08:15:42 +0800
committerFelix Lange <fjl@twurst.com>2016-04-12 21:58:07 +0800
commita9f26dcd0d14c0cb9f309ebccf81e8f741fc4636 (patch)
tree1a1e269194f3698737b0c1dbaf918c4e8dbae347 /accounts/account_manager.go
parentef63e9af55fcfe3255dddec3197bd8a807152c66 (diff)
downloadgo-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar.gz
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar.bz2
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar.lz
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar.xz
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.tar.zst
go-tangerine-a9f26dcd0d14c0cb9f309ebccf81e8f741fc4636.zip
accounts: cache key addresses
In order to avoid disk thrashing for Accounts and HasAccount, address->key file mappings are now cached in memory. This makes it no longer necessary to keep the key address in the file name. The address of each key is derived from file content instead. There are minor user-visible changes: - "geth account list" now reports key file paths alongside the address. - If multiple keys are present for an address, unlocking by address is not possible. Users are directed to remove the duplicate files instead. Unlocking by index is still possible. - Key files are overwritten written in place when updating the password.
Diffstat (limited to 'accounts/account_manager.go')
-rw-r--r--accounts/account_manager.go189
1 files changed, 111 insertions, 78 deletions
diff --git a/accounts/account_manager.go b/accounts/account_manager.go
index acf4d8e21..dc9f40048 100644
--- a/accounts/account_manager.go
+++ b/accounts/account_manager.go
@@ -22,8 +22,12 @@ package accounts
import (
"crypto/ecdsa"
crand "crypto/rand"
+ "encoding/json"
"errors"
"fmt"
+ "os"
+ "path/filepath"
+ "runtime"
"sync"
"time"
@@ -32,22 +36,28 @@ import (
)
var (
- ErrLocked = errors.New("account is locked")
- ErrNoKeys = errors.New("no keys in store")
+ ErrLocked = errors.New("account is locked")
+ ErrNoMatch = errors.New("no key for given address or file")
)
type Account struct {
Address common.Address
+ File string
}
func (acc *Account) MarshalJSON() ([]byte, error) {
return []byte(`"` + acc.Address.Hex() + `"`), nil
}
+func (acc *Account) UnmarshalJSON(raw []byte) error {
+ return json.Unmarshal(raw, &acc.Address)
+}
+
type Manager struct {
+ cache *addrCache
keyStore keyStore
+ mu sync.RWMutex
unlocked map[common.Address]*unlocked
- mutex sync.RWMutex
}
type unlocked struct {
@@ -56,36 +66,62 @@ type unlocked struct {
}
func NewManager(keydir string, scryptN, scryptP int) *Manager {
- return &Manager{
- keyStore: newKeyStorePassphrase(keydir, scryptN, scryptP),
- unlocked: make(map[common.Address]*unlocked),
- }
+ keydir, _ = filepath.Abs(keydir)
+ am := &Manager{keyStore: &keyStorePassphrase{keydir, scryptN, scryptP}}
+ am.init(keydir)
+ return am
}
func NewPlaintextManager(keydir string) *Manager {
- return &Manager{
- keyStore: newKeyStorePlain(keydir),
- unlocked: make(map[common.Address]*unlocked),
- }
+ keydir, _ = filepath.Abs(keydir)
+ am := &Manager{keyStore: &keyStorePlain{keydir}}
+ am.init(keydir)
+ return am
+}
+
+func (am *Manager) init(keydir string) {
+ am.unlocked = make(map[common.Address]*unlocked)
+ am.cache = newAddrCache(keydir)
+ // TODO: In order for this finalizer to work, there must be no references
+ // to am. addrCache doesn't keep a reference but unlocked keys do,
+ // so the finalizer will not trigger until all timed unlocks have expired.
+ runtime.SetFinalizer(am, func(m *Manager) {
+ m.cache.close()
+ })
}
func (am *Manager) HasAddress(addr common.Address) bool {
- accounts := am.Accounts()
- for _, acct := range accounts {
- if acct.Address == addr {
- return true
- }
- }
- return false
+ return am.cache.hasAddress(addr)
+}
+
+func (am *Manager) Accounts() []Account {
+ return am.cache.accounts()
}
func (am *Manager) DeleteAccount(a Account, auth string) error {
- return am.keyStore.DeleteKey(a.Address, auth)
+ // Decrypting the key isn't really necessary, but we do
+ // it anyway to check the password and zero out the key
+ // immediately afterwards.
+ a, key, err := am.getDecryptedKey(a, auth)
+ if key != nil {
+ zeroKey(key.PrivateKey)
+ }
+ if err != nil {
+ return err
+ }
+ // The order is crucial here. The key is dropped from the
+ // cache after the file is gone so that a reload happening in
+ // between won't insert it into the cache again.
+ err = os.Remove(a.File)
+ if err == nil {
+ am.cache.delete(a)
+ }
+ return err
}
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
- am.mutex.RLock()
- defer am.mutex.RUnlock()
+ am.mu.RLock()
+ defer am.mu.RUnlock()
unlockedKey, found := am.unlocked[a.Address]
if !found {
return nil, ErrLocked
@@ -100,12 +136,12 @@ func (am *Manager) Unlock(a Account, keyAuth string) error {
}
func (am *Manager) Lock(addr common.Address) error {
- am.mutex.Lock()
+ am.mu.Lock()
if unl, found := am.unlocked[addr]; found {
- am.mutex.Unlock()
+ am.mu.Unlock()
am.expire(addr, unl, time.Duration(0)*time.Nanosecond)
} else {
- am.mutex.Unlock()
+ am.mu.Unlock()
}
return nil
}
@@ -117,15 +153,14 @@ func (am *Manager) Lock(addr common.Address) error {
// If the accout is already unlocked, TimedUnlock extends or shortens
// the active unlock timeout.
func (am *Manager) TimedUnlock(a Account, keyAuth string, timeout time.Duration) error {
- key, err := am.keyStore.GetKey(a.Address, keyAuth)
+ _, key, err := am.getDecryptedKey(a, keyAuth)
if err != nil {
return err
}
- var u *unlocked
- am.mutex.Lock()
- defer am.mutex.Unlock()
- var found bool
- u, found = am.unlocked[a.Address]
+
+ am.mu.Lock()
+ defer am.mu.Unlock()
+ u, found := am.unlocked[a.Address]
if found {
// terminate dropLater for this key to avoid unexpected drops.
if u.abort != nil {
@@ -142,6 +177,18 @@ func (am *Manager) TimedUnlock(a Account, keyAuth string, timeout time.Duration)
return nil
}
+func (am *Manager) getDecryptedKey(a Account, auth string) (Account, *Key, error) {
+ am.cache.maybeReload()
+ am.cache.mu.Lock()
+ a, err := am.cache.find(a)
+ am.cache.mu.Unlock()
+ if err != nil {
+ return a, nil, err
+ }
+ key, err := am.keyStore.GetKey(a.Address, a.File, auth)
+ return a, key, err
+}
+
func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duration) {
t := time.NewTimer(timeout)
defer t.Stop()
@@ -149,7 +196,7 @@ func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duratio
case <-u.abort:
// just quit
case <-t.C:
- am.mutex.Lock()
+ am.mu.Lock()
// only drop if it's still the same key instance that dropLater
// was launched with. we can check that using pointer equality
// because the map stores a new pointer every time the key is
@@ -158,52 +205,33 @@ func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duratio
zeroKey(u.PrivateKey)
delete(am.unlocked, addr)
}
- am.mutex.Unlock()
+ am.mu.Unlock()
}
}
func (am *Manager) NewAccount(auth string) (Account, error) {
- key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
+ _, account, err := storeNewKey(am.keyStore, crand.Reader, auth)
if err != nil {
return Account{}, err
}
- return Account{Address: key.Address}, nil
+ // Add the account to the cache immediately rather
+ // than waiting for file system notifications to pick it up.
+ am.cache.add(account)
+ return account, nil
}
func (am *Manager) AccountByIndex(index int) (Account, error) {
- addrs, err := am.keyStore.GetKeyAddresses()
- if err != nil {
- return Account{}, err
- }
- if index < 0 || index >= len(addrs) {
- return Account{}, fmt.Errorf("account index %d not in range [0, %d]", index, len(addrs)-1)
- }
- return Account{Address: addrs[index]}, nil
-}
-
-func (am *Manager) Accounts() []Account {
- addresses, _ := am.keyStore.GetKeyAddresses()
- accounts := make([]Account, len(addresses))
- for i, addr := range addresses {
- accounts[i] = Account{
- Address: addr,
- }
- }
- return accounts
-}
-
-// zeroKey zeroes a private key in memory.
-func zeroKey(k *ecdsa.PrivateKey) {
- b := k.D.Bits()
- for i := range b {
- b[i] = 0
+ accounts := am.Accounts()
+ if index < 0 || index >= len(accounts) {
+ return Account{}, fmt.Errorf("account index %d out of range [0, %d]", index, len(accounts)-1)
}
+ return accounts[index], nil
}
// USE WITH CAUTION = this will save an unencrypted private key on disk
// no cli or js interface
func (am *Manager) Export(path string, a Account, keyAuth string) error {
- key, err := am.keyStore.GetKey(a.Address, keyAuth)
+ _, key, err := am.getDecryptedKey(a, keyAuth)
if err != nil {
return err
}
@@ -220,30 +248,35 @@ func (am *Manager) Import(path string, keyAuth string) (Account, error) {
func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, keyAuth string) (Account, error) {
key := newKeyFromECDSA(priv)
- if err := am.keyStore.StoreKey(key, keyAuth); err != nil {
+ a := Account{Address: key.Address, File: am.keyStore.JoinPath(keyFileName(key.Address))}
+ if err := am.keyStore.StoreKey(a.File, key, keyAuth); err != nil {
return Account{}, err
}
- return Account{Address: key.Address}, nil
+ am.cache.add(a)
+ return a, nil
}
-func (am *Manager) Update(a Account, authFrom, authTo string) (err error) {
- var key *Key
- key, err = am.keyStore.GetKey(a.Address, authFrom)
-
- if err == nil {
- err = am.keyStore.StoreKey(key, authTo)
- if err == nil {
- am.keyStore.Cleanup(a.Address)
- }
+func (am *Manager) Update(a Account, authFrom, authTo string) error {
+ a, key, err := am.getDecryptedKey(a, authFrom)
+ if err != nil {
+ return err
}
- return
+ return am.keyStore.StoreKey(a.File, key, authTo)
}
-func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) {
- var key *Key
- key, err = importPreSaleKey(am.keyStore, keyJSON, password)
+func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (Account, error) {
+ a, _, err := importPreSaleKey(am.keyStore, keyJSON, password)
if err != nil {
- return
+ return a, err
+ }
+ am.cache.add(a)
+ return a, nil
+}
+
+// zeroKey zeroes a private key in memory.
+func zeroKey(k *ecdsa.PrivateKey) {
+ b := k.D.Bits()
+ for i := range b {
+ b[i] = 0
}
- return Account{Address: key.Address}, nil
}