github.com/halybang/go-ethereum@v1.0.5-0.20180325041310-3b262bc1367c/accounts/keystore/account_cache.go (about)

     1  // Copyright 2017 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 keystore
    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/wanchain/go-wanchain/accounts"
    32  	"github.com/wanchain/go-wanchain/common"
    33  	"github.com/wanchain/go-wanchain/log"
    34  	"gopkg.in/fatih/set.v0"
    35  )
    36  
    37  // Minimum amount of time between cache reloads. This limit applies if the platform does
    38  // not support change notifications. It also applies if the keystore directory does not
    39  // exist yet, the code will attempt to create a watcher at most this often.
    40  const minReloadInterval = 2 * time.Second
    41  
    42  type accountsByURL []accounts.Account
    43  
    44  func (s accountsByURL) Len() int           { return len(s) }
    45  func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
    46  func (s accountsByURL) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    47  
    48  // AmbiguousAddrError is returned when attempting to unlock
    49  // an address for which more than one file exists.
    50  type AmbiguousAddrError struct {
    51  	Addr    common.Address
    52  	Matches []accounts.Account
    53  }
    54  
    55  func (err *AmbiguousAddrError) Error() string {
    56  	files := ""
    57  	for i, a := range err.Matches {
    58  		files += a.URL.Path
    59  		if i < len(err.Matches)-1 {
    60  			files += ", "
    61  		}
    62  	}
    63  	return fmt.Sprintf("multiple keys match address (%s)", files)
    64  }
    65  
    66  // accountCache is a live index of all accounts in the keystore.
    67  type accountCache struct {
    68  	keydir   string
    69  	watcher  *watcher
    70  	mu       sync.Mutex
    71  	all      accountsByURL
    72  	byAddr   map[common.Address][]accounts.Account
    73  	throttle *time.Timer
    74  	notify   chan struct{}
    75  	fileC    fileCache
    76  }
    77  
    78  // fileCache is a cache of files seen during scan of keystore
    79  type fileCache struct {
    80  	all   *set.SetNonTS // list of all files
    81  	mtime time.Time     // latest mtime seen
    82  	mu    sync.RWMutex
    83  }
    84  
    85  func newAccountCache(keydir string) (*accountCache, chan struct{}) {
    86  	ac := &accountCache{
    87  		keydir: keydir,
    88  		byAddr: make(map[common.Address][]accounts.Account),
    89  		notify: make(chan struct{}, 1),
    90  		fileC:  fileCache{all: set.NewNonTS()},
    91  	}
    92  	ac.watcher = newWatcher(ac)
    93  	return ac, ac.notify
    94  }
    95  
    96  func (ac *accountCache) accounts() []accounts.Account {
    97  	ac.maybeReload()
    98  	ac.mu.Lock()
    99  	defer ac.mu.Unlock()
   100  	cpy := make([]accounts.Account, len(ac.all))
   101  	copy(cpy, ac.all)
   102  	return cpy
   103  }
   104  
   105  func (ac *accountCache) hasAddress(addr common.Address) bool {
   106  	ac.maybeReload()
   107  	ac.mu.Lock()
   108  	defer ac.mu.Unlock()
   109  	return len(ac.byAddr[addr]) > 0
   110  }
   111  
   112  func (ac *accountCache) add(newAccount accounts.Account) {
   113  	ac.mu.Lock()
   114  	defer ac.mu.Unlock()
   115  
   116  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
   117  	if i < len(ac.all) && ac.all[i] == newAccount {
   118  		return
   119  	}
   120  	// newAccount is not in the cache.
   121  	ac.all = append(ac.all, accounts.Account{})
   122  	copy(ac.all[i+1:], ac.all[i:])
   123  	ac.all[i] = newAccount
   124  	ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
   125  }
   126  
   127  // note: removed needs to be unique here (i.e. both File and Address must be set).
   128  func (ac *accountCache) delete(removed accounts.Account) {
   129  	ac.mu.Lock()
   130  	defer ac.mu.Unlock()
   131  
   132  	ac.all = removeAccount(ac.all, removed)
   133  	if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   134  		delete(ac.byAddr, removed.Address)
   135  	} else {
   136  		ac.byAddr[removed.Address] = ba
   137  	}
   138  }
   139  
   140  // deleteByFile removes an account referenced by the given path.
   141  func (ac *accountCache) deleteByFile(path string) {
   142  	ac.mu.Lock()
   143  	defer ac.mu.Unlock()
   144  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
   145  
   146  	if i < len(ac.all) && ac.all[i].URL.Path == path {
   147  		removed := ac.all[i]
   148  		ac.all = append(ac.all[:i], ac.all[i+1:]...)
   149  		if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   150  			delete(ac.byAddr, removed.Address)
   151  		} else {
   152  			ac.byAddr[removed.Address] = ba
   153  		}
   154  	}
   155  }
   156  
   157  func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
   158  	for i := range slice {
   159  		if slice[i] == elem {
   160  			return append(slice[:i], slice[i+1:]...)
   161  		}
   162  	}
   163  	return slice
   164  }
   165  
   166  // find returns the cached account for address if there is a unique match.
   167  // The exact matching rules are explained by the documentation of accounts.Account.
   168  // Callers must hold ac.mu.
   169  func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
   170  	// Limit search to address candidates if possible.
   171  	matches := ac.all
   172  	if (a.Address != common.Address{}) {
   173  		matches = ac.byAddr[a.Address]
   174  	}
   175  	if a.URL.Path != "" {
   176  		// If only the basename is specified, complete the path.
   177  		if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
   178  			a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
   179  		}
   180  		for i := range matches {
   181  			if matches[i].URL == a.URL {
   182  				return matches[i], nil
   183  			}
   184  		}
   185  		if (a.Address == common.Address{}) {
   186  			return accounts.Account{}, ErrNoMatch
   187  		}
   188  	}
   189  	switch len(matches) {
   190  	case 1:
   191  		return matches[0], nil
   192  	case 0:
   193  		return accounts.Account{}, ErrNoMatch
   194  	default:
   195  		err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
   196  		copy(err.Matches, matches)
   197  		sort.Sort(accountsByURL(err.Matches))
   198  		return accounts.Account{}, err
   199  	}
   200  }
   201  
   202  func (ac *accountCache) maybeReload() {
   203  	ac.mu.Lock()
   204  
   205  	if ac.watcher.running {
   206  		ac.mu.Unlock()
   207  		return // A watcher is running and will keep the cache up-to-date.
   208  	}
   209  	if ac.throttle == nil {
   210  		ac.throttle = time.NewTimer(0)
   211  	} else {
   212  		select {
   213  		case <-ac.throttle.C:
   214  		default:
   215  			ac.mu.Unlock()
   216  			return // The cache was reloaded recently.
   217  		}
   218  	}
   219  	// No watcher running, start it.
   220  	ac.watcher.start()
   221  	ac.throttle.Reset(minReloadInterval)
   222  	ac.mu.Unlock()
   223  	ac.scanAccounts()
   224  }
   225  
   226  func (ac *accountCache) close() {
   227  	ac.mu.Lock()
   228  	ac.watcher.close()
   229  	if ac.throttle != nil {
   230  		ac.throttle.Stop()
   231  	}
   232  	if ac.notify != nil {
   233  		close(ac.notify)
   234  		ac.notify = nil
   235  	}
   236  	ac.mu.Unlock()
   237  }
   238  
   239  // scanFiles performs a new scan on the given directory, compares against the already
   240  // cached filenames, and returns file sets: new, missing , modified
   241  func (fc *fileCache) scanFiles(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
   242  	t0 := time.Now()
   243  	files, err := ioutil.ReadDir(keyDir)
   244  	t1 := time.Now()
   245  	if err != nil {
   246  		return nil, nil, nil, err
   247  	}
   248  	fc.mu.RLock()
   249  	prevMtime := fc.mtime
   250  	fc.mu.RUnlock()
   251  
   252  	filesNow := set.NewNonTS()
   253  	moddedFiles := set.NewNonTS()
   254  	var newMtime time.Time
   255  	for _, fi := range files {
   256  		modTime := fi.ModTime()
   257  		path := filepath.Join(keyDir, fi.Name())
   258  		if skipKeyFile(fi) {
   259  			log.Trace("Ignoring file on account scan", "path", path)
   260  			continue
   261  		}
   262  		filesNow.Add(path)
   263  		if modTime.After(prevMtime) {
   264  			moddedFiles.Add(path)
   265  		}
   266  		if modTime.After(newMtime) {
   267  			newMtime = modTime
   268  		}
   269  	}
   270  	t2 := time.Now()
   271  
   272  	fc.mu.Lock()
   273  	// Missing = previous - current
   274  	missing := set.Difference(fc.all, filesNow)
   275  	// New = current - previous
   276  	newFiles := set.Difference(filesNow, fc.all)
   277  	// Modified = modified - new
   278  	modified := set.Difference(moddedFiles, newFiles)
   279  	fc.all = filesNow
   280  	fc.mtime = newMtime
   281  	fc.mu.Unlock()
   282  	t3 := time.Now()
   283  	log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2))
   284  	return newFiles, missing, modified, nil
   285  }
   286  
   287  // scanAccounts checks if any changes have occurred on the filesystem, and
   288  // updates the account cache accordingly
   289  func (ac *accountCache) scanAccounts() error {
   290  	newFiles, missingFiles, modified, err := ac.fileC.scanFiles(ac.keydir)
   291  	t1 := time.Now()
   292  	if err != nil {
   293  		log.Debug("Failed to reload keystore contents", "err", err)
   294  		return err
   295  	}
   296  	var (
   297  		buf     = new(bufio.Reader)
   298  		keyJSON struct {
   299  			Address string `json:"address"`
   300  		}
   301  	)
   302  	readAccount := func(path string) *accounts.Account {
   303  		fd, err := os.Open(path)
   304  		if err != nil {
   305  			log.Trace("Failed to open keystore file", "path", path, "err", err)
   306  			return nil
   307  		}
   308  		defer fd.Close()
   309  		buf.Reset(fd)
   310  		// Parse the address.
   311  		keyJSON.Address = ""
   312  		err = json.NewDecoder(buf).Decode(&keyJSON)
   313  		addr := common.HexToAddress(keyJSON.Address)
   314  		switch {
   315  		case err != nil:
   316  			log.Debug("Failed to decode keystore key", "path", path, "err", err)
   317  		case (addr == common.Address{}):
   318  			log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
   319  		default:
   320  			return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
   321  		}
   322  		return nil
   323  	}
   324  
   325  	for _, p := range newFiles.List() {
   326  		path, _ := p.(string)
   327  		a := readAccount(path)
   328  		if a != nil {
   329  			ac.add(*a)
   330  		}
   331  	}
   332  	for _, p := range missingFiles.List() {
   333  		path, _ := p.(string)
   334  		ac.deleteByFile(path)
   335  	}
   336  
   337  	for _, p := range modified.List() {
   338  		path, _ := p.(string)
   339  		a := readAccount(path)
   340  		ac.deleteByFile(path)
   341  		if a != nil {
   342  			ac.add(*a)
   343  		}
   344  	}
   345  
   346  	t2 := time.Now()
   347  
   348  	select {
   349  	case ac.notify <- struct{}{}:
   350  	default:
   351  	}
   352  	log.Trace("Handled keystore changes", "time", t2.Sub(t1))
   353  
   354  	return nil
   355  }
   356  
   357  func skipKeyFile(fi os.FileInfo) bool {
   358  	// Skip editor backups and UNIX-style hidden files.
   359  	if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
   360  		return true
   361  	}
   362  	// Skip misc special files, directories (yes, symlinks too).
   363  	if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
   364  		return true
   365  	}
   366  	return false
   367  }