270 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			270 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2016 The go-ethereum Authors
							 | 
						||
| 
								 | 
							
								// This file is part of the go-ethereum library.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library is free software: you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
								// it under the terms of the GNU Lesser General Public License as published by
							 | 
						||
| 
								 | 
							
								// the Free Software Foundation, either version 3 of the License, or
							 | 
						||
| 
								 | 
							
								// (at your option) any later version.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library 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 Lesser General Public License for more details.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// You should have received a copy of the GNU Lesser General Public License
							 | 
						||
| 
								 | 
							
								// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package accounts
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"bufio"
							 | 
						||
| 
								 | 
							
									"encoding/json"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"io/ioutil"
							 | 
						||
| 
								 | 
							
									"os"
							 | 
						||
| 
								 | 
							
									"path/filepath"
							 | 
						||
| 
								 | 
							
									"sort"
							 | 
						||
| 
								 | 
							
									"strings"
							 | 
						||
| 
								 | 
							
									"sync"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/common"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/logger"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/logger/glog"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Minimum amount of time between cache reloads. This limit applies if the platform does
							 | 
						||
| 
								 | 
							
								// not support change notifications. It also applies if the keystore directory does not
							 | 
						||
| 
								 | 
							
								// exist yet, the code will attempt to create a watcher at most this often.
							 | 
						||
| 
								 | 
							
								const minReloadInterval = 2 * time.Second
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type accountsByFile []Account
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (s accountsByFile) Len() int           { return len(s) }
							 | 
						||
| 
								 | 
							
								func (s accountsByFile) Less(i, j int) bool { return s[i].File < s[j].File }
							 | 
						||
| 
								 | 
							
								func (s accountsByFile) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// AmbiguousAddrError is returned when attempting to unlock
							 | 
						||
| 
								 | 
							
								// an address for which more than one file exists.
							 | 
						||
| 
								 | 
							
								type AmbiguousAddrError struct {
							 | 
						||
| 
								 | 
							
									Addr    common.Address
							 | 
						||
| 
								 | 
							
									Matches []Account
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (err *AmbiguousAddrError) Error() string {
							 | 
						||
| 
								 | 
							
									files := ""
							 | 
						||
| 
								 | 
							
									for i, a := range err.Matches {
							 | 
						||
| 
								 | 
							
										files += a.File
							 | 
						||
| 
								 | 
							
										if i < len(err.Matches)-1 {
							 | 
						||
| 
								 | 
							
											files += ", "
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return fmt.Sprintf("multiple keys match address (%s)", files)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// addrCache is a live index of all accounts in the keystore.
							 | 
						||
| 
								 | 
							
								type addrCache struct {
							 | 
						||
| 
								 | 
							
									keydir   string
							 | 
						||
| 
								 | 
							
									watcher  *watcher
							 | 
						||
| 
								 | 
							
									mu       sync.Mutex
							 | 
						||
| 
								 | 
							
									all      accountsByFile
							 | 
						||
| 
								 | 
							
									byAddr   map[common.Address][]Account
							 | 
						||
| 
								 | 
							
									throttle *time.Timer
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func newAddrCache(keydir string) *addrCache {
							 | 
						||
| 
								 | 
							
									ac := &addrCache{
							 | 
						||
| 
								 | 
							
										keydir: keydir,
							 | 
						||
| 
								 | 
							
										byAddr: make(map[common.Address][]Account),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									ac.watcher = newWatcher(ac)
							 | 
						||
| 
								 | 
							
									return ac
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) accounts() []Account {
							 | 
						||
| 
								 | 
							
									ac.maybeReload()
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
									cpy := make([]Account, len(ac.all))
							 | 
						||
| 
								 | 
							
									copy(cpy, ac.all)
							 | 
						||
| 
								 | 
							
									return cpy
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) hasAddress(addr common.Address) bool {
							 | 
						||
| 
								 | 
							
									ac.maybeReload()
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
									return len(ac.byAddr[addr]) > 0
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) add(newAccount Account) {
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].File >= newAccount.File })
							 | 
						||
| 
								 | 
							
									if i < len(ac.all) && ac.all[i] == newAccount {
							 | 
						||
| 
								 | 
							
										return
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									// newAccount is not in the cache.
							 | 
						||
| 
								 | 
							
									ac.all = append(ac.all, Account{})
							 | 
						||
| 
								 | 
							
									copy(ac.all[i+1:], ac.all[i:])
							 | 
						||
| 
								 | 
							
									ac.all[i] = newAccount
							 | 
						||
| 
								 | 
							
									ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// note: removed needs to be unique here (i.e. both File and Address must be set).
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) delete(removed Account) {
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
									ac.all = removeAccount(ac.all, removed)
							 | 
						||
| 
								 | 
							
									if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
							 | 
						||
| 
								 | 
							
										delete(ac.byAddr, removed.Address)
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										ac.byAddr[removed.Address] = ba
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func removeAccount(slice []Account, elem Account) []Account {
							 | 
						||
| 
								 | 
							
									for i := range slice {
							 | 
						||
| 
								 | 
							
										if slice[i] == elem {
							 | 
						||
| 
								 | 
							
											return append(slice[:i], slice[i+1:]...)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return slice
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// find returns the cached account for address if there is a unique match.
							 | 
						||
| 
								 | 
							
								// The exact matching rules are explained by the documentation of Account.
							 | 
						||
| 
								 | 
							
								// Callers must hold ac.mu.
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) find(a Account) (Account, error) {
							 | 
						||
| 
								 | 
							
									// Limit search to address candidates if possible.
							 | 
						||
| 
								 | 
							
									matches := ac.all
							 | 
						||
| 
								 | 
							
									if (a.Address != common.Address{}) {
							 | 
						||
| 
								 | 
							
										matches = ac.byAddr[a.Address]
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if a.File != "" {
							 | 
						||
| 
								 | 
							
										// If only the basename is specified, complete the path.
							 | 
						||
| 
								 | 
							
										if !strings.ContainsRune(a.File, filepath.Separator) {
							 | 
						||
| 
								 | 
							
											a.File = filepath.Join(ac.keydir, a.File)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										for i := range matches {
							 | 
						||
| 
								 | 
							
											if matches[i].File == a.File {
							 | 
						||
| 
								 | 
							
												return matches[i], nil
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if (a.Address == common.Address{}) {
							 | 
						||
| 
								 | 
							
											return Account{}, ErrNoMatch
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									switch len(matches) {
							 | 
						||
| 
								 | 
							
									case 1:
							 | 
						||
| 
								 | 
							
										return matches[0], nil
							 | 
						||
| 
								 | 
							
									case 0:
							 | 
						||
| 
								 | 
							
										return Account{}, ErrNoMatch
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]Account, len(matches))}
							 | 
						||
| 
								 | 
							
										copy(err.Matches, matches)
							 | 
						||
| 
								 | 
							
										return Account{}, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) maybeReload() {
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									defer ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
									if ac.watcher.running {
							 | 
						||
| 
								 | 
							
										return // A watcher is running and will keep the cache up-to-date.
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if ac.throttle == nil {
							 | 
						||
| 
								 | 
							
										ac.throttle = time.NewTimer(0)
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										select {
							 | 
						||
| 
								 | 
							
										case <-ac.throttle.C:
							 | 
						||
| 
								 | 
							
										default:
							 | 
						||
| 
								 | 
							
											return // The cache was reloaded recently.
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									ac.watcher.start()
							 | 
						||
| 
								 | 
							
									ac.reload()
							 | 
						||
| 
								 | 
							
									ac.throttle.Reset(minReloadInterval)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) close() {
							 | 
						||
| 
								 | 
							
									ac.mu.Lock()
							 | 
						||
| 
								 | 
							
									ac.watcher.close()
							 | 
						||
| 
								 | 
							
									if ac.throttle != nil {
							 | 
						||
| 
								 | 
							
										ac.throttle.Stop()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									ac.mu.Unlock()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// reload caches addresses of existing accounts.
							 | 
						||
| 
								 | 
							
								// Callers must hold ac.mu.
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) reload() {
							 | 
						||
| 
								 | 
							
									accounts, err := ac.scan()
							 | 
						||
| 
								 | 
							
									if err != nil && glog.V(logger.Debug) {
							 | 
						||
| 
								 | 
							
										glog.Errorf("can't load keys: %v", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									ac.all = accounts
							 | 
						||
| 
								 | 
							
									sort.Sort(ac.all)
							 | 
						||
| 
								 | 
							
									for k := range ac.byAddr {
							 | 
						||
| 
								 | 
							
										delete(ac.byAddr, k)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range accounts {
							 | 
						||
| 
								 | 
							
										ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									glog.V(logger.Debug).Infof("reloaded keys, cache has %d accounts", len(ac.all))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (ac *addrCache) scan() ([]Account, error) {
							 | 
						||
| 
								 | 
							
									files, err := ioutil.ReadDir(ac.keydir)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var (
							 | 
						||
| 
								 | 
							
										buf     = new(bufio.Reader)
							 | 
						||
| 
								 | 
							
										addrs   []Account
							 | 
						||
| 
								 | 
							
										keyJSON struct {
							 | 
						||
| 
								 | 
							
											Address common.Address `json:"address"`
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									)
							 | 
						||
| 
								 | 
							
									for _, fi := range files {
							 | 
						||
| 
								 | 
							
										path := filepath.Join(ac.keydir, fi.Name())
							 | 
						||
| 
								 | 
							
										if skipKeyFile(fi) {
							 | 
						||
| 
								 | 
							
											glog.V(logger.Detail).Infof("ignoring file %s", path)
							 | 
						||
| 
								 | 
							
											continue
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										fd, err := os.Open(path)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											glog.V(logger.Detail).Infoln(err)
							 | 
						||
| 
								 | 
							
											continue
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										buf.Reset(fd)
							 | 
						||
| 
								 | 
							
										// Parse the address.
							 | 
						||
| 
								 | 
							
										keyJSON.Address = common.Address{}
							 | 
						||
| 
								 | 
							
										err = json.NewDecoder(buf).Decode(&keyJSON)
							 | 
						||
| 
								 | 
							
										switch {
							 | 
						||
| 
								 | 
							
										case err != nil:
							 | 
						||
| 
								 | 
							
											glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err)
							 | 
						||
| 
								 | 
							
										case (keyJSON.Address == common.Address{}):
							 | 
						||
| 
								 | 
							
											glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
							 | 
						||
| 
								 | 
							
										default:
							 | 
						||
| 
								 | 
							
											addrs = append(addrs, Account{Address: keyJSON.Address, File: path})
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										fd.Close()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return addrs, err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func skipKeyFile(fi os.FileInfo) bool {
							 | 
						||
| 
								 | 
							
									// Skip editor backups and UNIX-style hidden files.
							 | 
						||
| 
								 | 
							
									if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
							 | 
						||
| 
								 | 
							
										return true
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									// Skip misc special files, directories (yes, symlinks too).
							 | 
						||
| 
								 | 
							
									if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
							 | 
						||
| 
								 | 
							
										return true
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return false
							 | 
						||
| 
								 | 
							
								}
							 |