github.com/murrekatt/go-ethereum@v1.5.8-0.20170123175102-fc52f2c007fb/accounts/addrcache.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package accounts
    18  
    19  import (
    20  	"bufio"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"sort"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/logger"
    33  	"github.com/ethereum/go-ethereum/logger/glog"
    34  )
    35  
    36  // Minimum amount of time between cache reloads. This limit applies if the platform does
    37  // not support change notifications. It also applies if the keystore directory does not
    38  // exist yet, the code will attempt to create a watcher at most this often.
    39  const minReloadInterval = 2 * time.Second
    40  
    41  type accountsByFile []Account
    42  
    43  func (s accountsByFile) Len() int           { return len(s) }
    44  func (s accountsByFile) Less(i, j int) bool { return s[i].File < s[j].File }
    45  func (s accountsByFile) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    46  
    47  // AmbiguousAddrError is returned when attempting to unlock
    48  // an address for which more than one file exists.
    49  type AmbiguousAddrError struct {
    50  	Addr    common.Address
    51  	Matches []Account
    52  }
    53  
    54  func (err *AmbiguousAddrError) Error() string {
    55  	files := ""
    56  	for i, a := range err.Matches {
    57  		files += a.File
    58  		if i < len(err.Matches)-1 {
    59  			files += ", "
    60  		}
    61  	}
    62  	return fmt.Sprintf("multiple keys match address (%s)", files)
    63  }
    64  
    65  // addrCache is a live index of all accounts in the keystore.
    66  type addrCache struct {
    67  	keydir   string
    68  	watcher  *watcher
    69  	mu       sync.Mutex
    70  	all      accountsByFile
    71  	byAddr   map[common.Address][]Account
    72  	throttle *time.Timer
    73  }
    74  
    75  func newAddrCache(keydir string) *addrCache {
    76  	ac := &addrCache{
    77  		keydir: keydir,
    78  		byAddr: make(map[common.Address][]Account),
    79  	}
    80  	ac.watcher = newWatcher(ac)
    81  	return ac
    82  }
    83  
    84  func (ac *addrCache) accounts() []Account {
    85  	ac.maybeReload()
    86  	ac.mu.Lock()
    87  	defer ac.mu.Unlock()
    88  	cpy := make([]Account, len(ac.all))
    89  	copy(cpy, ac.all)
    90  	return cpy
    91  }
    92  
    93  func (ac *addrCache) hasAddress(addr common.Address) bool {
    94  	ac.maybeReload()
    95  	ac.mu.Lock()
    96  	defer ac.mu.Unlock()
    97  	return len(ac.byAddr[addr]) > 0
    98  }
    99  
   100  func (ac *addrCache) add(newAccount Account) {
   101  	ac.mu.Lock()
   102  	defer ac.mu.Unlock()
   103  
   104  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].File >= newAccount.File })
   105  	if i < len(ac.all) && ac.all[i] == newAccount {
   106  		return
   107  	}
   108  	// newAccount is not in the cache.
   109  	ac.all = append(ac.all, Account{})
   110  	copy(ac.all[i+1:], ac.all[i:])
   111  	ac.all[i] = newAccount
   112  	ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
   113  }
   114  
   115  // note: removed needs to be unique here (i.e. both File and Address must be set).
   116  func (ac *addrCache) delete(removed Account) {
   117  	ac.mu.Lock()
   118  	defer ac.mu.Unlock()
   119  	ac.all = removeAccount(ac.all, removed)
   120  	if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   121  		delete(ac.byAddr, removed.Address)
   122  	} else {
   123  		ac.byAddr[removed.Address] = ba
   124  	}
   125  }
   126  
   127  func removeAccount(slice []Account, elem Account) []Account {
   128  	for i := range slice {
   129  		if slice[i] == elem {
   130  			return append(slice[:i], slice[i+1:]...)
   131  		}
   132  	}
   133  	return slice
   134  }
   135  
   136  // find returns the cached account for address if there is a unique match.
   137  // The exact matching rules are explained by the documentation of Account.
   138  // Callers must hold ac.mu.
   139  func (ac *addrCache) find(a Account) (Account, error) {
   140  	// Limit search to address candidates if possible.
   141  	matches := ac.all
   142  	if (a.Address != common.Address{}) {
   143  		matches = ac.byAddr[a.Address]
   144  	}
   145  	if a.File != "" {
   146  		// If only the basename is specified, complete the path.
   147  		if !strings.ContainsRune(a.File, filepath.Separator) {
   148  			a.File = filepath.Join(ac.keydir, a.File)
   149  		}
   150  		for i := range matches {
   151  			if matches[i].File == a.File {
   152  				return matches[i], nil
   153  			}
   154  		}
   155  		if (a.Address == common.Address{}) {
   156  			return Account{}, ErrNoMatch
   157  		}
   158  	}
   159  	switch len(matches) {
   160  	case 1:
   161  		return matches[0], nil
   162  	case 0:
   163  		return Account{}, ErrNoMatch
   164  	default:
   165  		err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]Account, len(matches))}
   166  		copy(err.Matches, matches)
   167  		return Account{}, err
   168  	}
   169  }
   170  
   171  func (ac *addrCache) maybeReload() {
   172  	ac.mu.Lock()
   173  	defer ac.mu.Unlock()
   174  	if ac.watcher.running {
   175  		return // A watcher is running and will keep the cache up-to-date.
   176  	}
   177  	if ac.throttle == nil {
   178  		ac.throttle = time.NewTimer(0)
   179  	} else {
   180  		select {
   181  		case <-ac.throttle.C:
   182  		default:
   183  			return // The cache was reloaded recently.
   184  		}
   185  	}
   186  	ac.watcher.start()
   187  	ac.reload()
   188  	ac.throttle.Reset(minReloadInterval)
   189  }
   190  
   191  func (ac *addrCache) close() {
   192  	ac.mu.Lock()
   193  	ac.watcher.close()
   194  	if ac.throttle != nil {
   195  		ac.throttle.Stop()
   196  	}
   197  	ac.mu.Unlock()
   198  }
   199  
   200  // reload caches addresses of existing accounts.
   201  // Callers must hold ac.mu.
   202  func (ac *addrCache) reload() {
   203  	accounts, err := ac.scan()
   204  	if err != nil && glog.V(logger.Debug) {
   205  		glog.Errorf("can't load keys: %v", err)
   206  	}
   207  	ac.all = accounts
   208  	sort.Sort(ac.all)
   209  	for k := range ac.byAddr {
   210  		delete(ac.byAddr, k)
   211  	}
   212  	for _, a := range accounts {
   213  		ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a)
   214  	}
   215  	glog.V(logger.Debug).Infof("reloaded keys, cache has %d accounts", len(ac.all))
   216  }
   217  
   218  func (ac *addrCache) scan() ([]Account, error) {
   219  	files, err := ioutil.ReadDir(ac.keydir)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	var (
   225  		buf     = new(bufio.Reader)
   226  		addrs   []Account
   227  		keyJSON struct {
   228  			Address string `json:"address"`
   229  		}
   230  	)
   231  	for _, fi := range files {
   232  		path := filepath.Join(ac.keydir, fi.Name())
   233  		if skipKeyFile(fi) {
   234  			glog.V(logger.Detail).Infof("ignoring file %s", path)
   235  			continue
   236  		}
   237  		fd, err := os.Open(path)
   238  		if err != nil {
   239  			glog.V(logger.Detail).Infoln(err)
   240  			continue
   241  		}
   242  		buf.Reset(fd)
   243  		// Parse the address.
   244  		keyJSON.Address = ""
   245  		err = json.NewDecoder(buf).Decode(&keyJSON)
   246  		addr := common.HexToAddress(keyJSON.Address)
   247  		switch {
   248  		case err != nil:
   249  			glog.V(logger.Debug).Infof("can't decode key %s: %v", path, err)
   250  		case (addr == common.Address{}):
   251  			glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path)
   252  		default:
   253  			addrs = append(addrs, Account{Address: addr, File: path})
   254  		}
   255  		fd.Close()
   256  	}
   257  	return addrs, err
   258  }
   259  
   260  func skipKeyFile(fi os.FileInfo) bool {
   261  	// Skip editor backups and UNIX-style hidden files.
   262  	if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
   263  		return true
   264  	}
   265  	// Skip misc special files, directories (yes, symlinks too).
   266  	if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
   267  		return true
   268  	}
   269  	return false
   270  }