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.
This commit is contained in:
Felix Lange
2016-03-03 01:15:42 +01:00
parent ef63e9af55
commit a9f26dcd0d
18 changed files with 1060 additions and 371 deletions

View File

@ -17,14 +17,10 @@
package accounts
import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/ethereum/go-ethereum/common"
)
@ -33,167 +29,34 @@ type keyStorePlain struct {
keysDirPath string
}
func newKeyStorePlain(path string) keyStore {
return &keyStorePlain{path}
}
func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) {
return generateNewKeyDefault(ks, rand, auth)
}
func generateNewKeyDefault(ks keyStore, rand io.Reader, auth string) (key *Key, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("GenerateNewKey error: %v", r)
}
}()
key = NewKey(rand)
err = ks.StoreKey(key, auth)
return key, err
}
func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (*Key, error) {
keyjson, err := getKeyFile(ks.keysDirPath, keyAddr)
func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
fd, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fd.Close()
key := new(Key)
if err := json.Unmarshal(keyjson, key); err != nil {
if err := json.NewDecoder(fd).Decode(key); err != nil {
return nil, err
}
if key.Address != addr {
return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
}
return key, nil
}
func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
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) {
keyJSON, err := json.Marshal(key)
if err != nil {
return
}
err = writeKeyFile(key.Address, ks.keysDirPath, keyJSON)
return
}
func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
return deleteKey(ks.keysDirPath, keyAddr)
}
func deleteKey(keysDirPath string, keyAddr common.Address) (err error) {
var path string
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 getKeyFilePath(keysDirPath string, keyAddr common.Address) (keyFilePath string, err error) {
addrHex := hex.EncodeToString(keyAddr[:])
matches, err := filepath.Glob(filepath.Join(keysDirPath, fmt.Sprintf("*--%s", addrHex)))
if len(matches) > 0 {
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)
func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
content, err := json.Marshal(key)
if err != nil {
return err
}
// read, write for user
return ioutil.WriteFile(filepath.Join(keysDirPath, filename), content, 0600)
return writeKeyFile(filename, content)
}
// 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"
func (ks keyStorePlain) JoinPath(filename string) string {
if filepath.IsAbs(filename) {
return filename
} else {
tz = fmt.Sprintf("%03d00", offset/3600)
return filepath.Join(ks.keysDirPath, filename)
}
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)
if err != nil {
return nil, err
}
for _, fileInfo := range fileInfos {
filename := fileInfo.Name()
if len(filename) >= 40 {
addr := filename[len(filename)-40 : len(filename)]
address, err := hex.DecodeString(addr)
if err == nil {
addresses = append(addresses, common.BytesToAddress(address))
}
}
}
return addresses, err
}