github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/keystore/account_cache.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  package keystore
    13  
    14  import (
    15  	"bufio"
    16  	"encoding/json"
    17  	"fmt"
    18  	"os"
    19  	"path/filepath"
    20  	"sort"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/Sberex/go-sberex/accounts"
    26  	"github.com/Sberex/go-sberex/common"
    27  	"github.com/Sberex/go-sberex/log"
    28  	"gopkg.in/fatih/set.v0"
    29  )
    30  
    31  // Minimum amount of time between cache reloads. This limit applies if the platform does
    32  // not support change notifications. It also applies if the keystore directory does not
    33  // exist yet, the code will attempt to create a watcher at most this often.
    34  const minReloadInterval = 2 * time.Second
    35  
    36  type accountsByURL []accounts.Account
    37  
    38  func (s accountsByURL) Len() int           { return len(s) }
    39  func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
    40  func (s accountsByURL) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    41  
    42  // AmbiguousAddrError is returned when attempting to unlock
    43  // an address for which more than one file exists.
    44  type AmbiguousAddrError struct {
    45  	Addr    common.Address
    46  	Matches []accounts.Account
    47  }
    48  
    49  func (err *AmbiguousAddrError) Error() string {
    50  	files := ""
    51  	for i, a := range err.Matches {
    52  		files += a.URL.Path
    53  		if i < len(err.Matches)-1 {
    54  			files += ", "
    55  		}
    56  	}
    57  	return fmt.Sprintf("multiple keys match address (%s)", files)
    58  }
    59  
    60  // accountCache is a live index of all accounts in the keystore.
    61  type accountCache struct {
    62  	keydir   string
    63  	watcher  *watcher
    64  	mu       sync.Mutex
    65  	all      accountsByURL
    66  	byAddr   map[common.Address][]accounts.Account
    67  	throttle *time.Timer
    68  	notify   chan struct{}
    69  	fileC    fileCache
    70  }
    71  
    72  func newAccountCache(keydir string) (*accountCache, chan struct{}) {
    73  	ac := &accountCache{
    74  		keydir: keydir,
    75  		byAddr: make(map[common.Address][]accounts.Account),
    76  		notify: make(chan struct{}, 1),
    77  		fileC:  fileCache{all: set.NewNonTS()},
    78  	}
    79  	ac.watcher = newWatcher(ac)
    80  	return ac, ac.notify
    81  }
    82  
    83  func (ac *accountCache) accounts() []accounts.Account {
    84  	ac.maybeReload()
    85  	ac.mu.Lock()
    86  	defer ac.mu.Unlock()
    87  	cpy := make([]accounts.Account, len(ac.all))
    88  	copy(cpy, ac.all)
    89  	return cpy
    90  }
    91  
    92  func (ac *accountCache) hasAddress(addr common.Address) bool {
    93  	ac.maybeReload()
    94  	ac.mu.Lock()
    95  	defer ac.mu.Unlock()
    96  	return len(ac.byAddr[addr]) > 0
    97  }
    98  
    99  func (ac *accountCache) add(newAccount accounts.Account) {
   100  	ac.mu.Lock()
   101  	defer ac.mu.Unlock()
   102  
   103  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
   104  	if i < len(ac.all) && ac.all[i] == newAccount {
   105  		return
   106  	}
   107  	// newAccount is not in the cache.
   108  	ac.all = append(ac.all, accounts.Account{})
   109  	copy(ac.all[i+1:], ac.all[i:])
   110  	ac.all[i] = newAccount
   111  	ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
   112  }
   113  
   114  // note: removed needs to be unique here (i.e. both File and Address must be set).
   115  func (ac *accountCache) delete(removed accounts.Account) {
   116  	ac.mu.Lock()
   117  	defer ac.mu.Unlock()
   118  
   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  // deleteByFile removes an account referenced by the given path.
   128  func (ac *accountCache) deleteByFile(path string) {
   129  	ac.mu.Lock()
   130  	defer ac.mu.Unlock()
   131  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
   132  
   133  	if i < len(ac.all) && ac.all[i].URL.Path == path {
   134  		removed := ac.all[i]
   135  		ac.all = append(ac.all[:i], ac.all[i+1:]...)
   136  		if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   137  			delete(ac.byAddr, removed.Address)
   138  		} else {
   139  			ac.byAddr[removed.Address] = ba
   140  		}
   141  	}
   142  }
   143  
   144  func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
   145  	for i := range slice {
   146  		if slice[i] == elem {
   147  			return append(slice[:i], slice[i+1:]...)
   148  		}
   149  	}
   150  	return slice
   151  }
   152  
   153  // find returns the cached account for address if there is a unique match.
   154  // The exact matching rules are explained by the documentation of accounts.Account.
   155  // Callers must hold ac.mu.
   156  func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
   157  	// Limit search to address candidates if possible.
   158  	matches := ac.all
   159  	if (a.Address != common.Address{}) {
   160  		matches = ac.byAddr[a.Address]
   161  	}
   162  	if a.URL.Path != "" {
   163  		// If only the basename is specified, complete the path.
   164  		if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
   165  			a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
   166  		}
   167  		for i := range matches {
   168  			if matches[i].URL == a.URL {
   169  				return matches[i], nil
   170  			}
   171  		}
   172  		if (a.Address == common.Address{}) {
   173  			return accounts.Account{}, ErrNoMatch
   174  		}
   175  	}
   176  	switch len(matches) {
   177  	case 1:
   178  		return matches[0], nil
   179  	case 0:
   180  		return accounts.Account{}, ErrNoMatch
   181  	default:
   182  		err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
   183  		copy(err.Matches, matches)
   184  		sort.Sort(accountsByURL(err.Matches))
   185  		return accounts.Account{}, err
   186  	}
   187  }
   188  
   189  func (ac *accountCache) maybeReload() {
   190  	ac.mu.Lock()
   191  
   192  	if ac.watcher.running {
   193  		ac.mu.Unlock()
   194  		return // A watcher is running and will keep the cache up-to-date.
   195  	}
   196  	if ac.throttle == nil {
   197  		ac.throttle = time.NewTimer(0)
   198  	} else {
   199  		select {
   200  		case <-ac.throttle.C:
   201  		default:
   202  			ac.mu.Unlock()
   203  			return // The cache was reloaded recently.
   204  		}
   205  	}
   206  	// No watcher running, start it.
   207  	ac.watcher.start()
   208  	ac.throttle.Reset(minReloadInterval)
   209  	ac.mu.Unlock()
   210  	ac.scanAccounts()
   211  }
   212  
   213  func (ac *accountCache) close() {
   214  	ac.mu.Lock()
   215  	ac.watcher.close()
   216  	if ac.throttle != nil {
   217  		ac.throttle.Stop()
   218  	}
   219  	if ac.notify != nil {
   220  		close(ac.notify)
   221  		ac.notify = nil
   222  	}
   223  	ac.mu.Unlock()
   224  }
   225  
   226  // scanAccounts checks if any changes have occurred on the filesystem, and
   227  // updates the account cache accordingly
   228  func (ac *accountCache) scanAccounts() error {
   229  	// Scan the entire folder metadata for file changes
   230  	creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
   231  	if err != nil {
   232  		log.Debug("Failed to reload keystore contents", "err", err)
   233  		return err
   234  	}
   235  	if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
   236  		return nil
   237  	}
   238  	// Create a helper method to scan the contents of the key files
   239  	var (
   240  		buf = new(bufio.Reader)
   241  		key struct {
   242  			Address string `json:"address"`
   243  		}
   244  	)
   245  	readAccount := func(path string) *accounts.Account {
   246  		fd, err := os.Open(path)
   247  		if err != nil {
   248  			log.Trace("Failed to open keystore file", "path", path, "err", err)
   249  			return nil
   250  		}
   251  		defer fd.Close()
   252  		buf.Reset(fd)
   253  		// Parse the address.
   254  		key.Address = ""
   255  		err = json.NewDecoder(buf).Decode(&key)
   256  		addr := common.HexToAddress(key.Address)
   257  		switch {
   258  		case err != nil:
   259  			log.Debug("Failed to decode keystore key", "path", path, "err", err)
   260  		case (addr == common.Address{}):
   261  			log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
   262  		default:
   263  			return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
   264  		}
   265  		return nil
   266  	}
   267  	// Process all the file diffs
   268  	start := time.Now()
   269  
   270  	for _, p := range creates.List() {
   271  		if a := readAccount(p.(string)); a != nil {
   272  			ac.add(*a)
   273  		}
   274  	}
   275  	for _, p := range deletes.List() {
   276  		ac.deleteByFile(p.(string))
   277  	}
   278  	for _, p := range updates.List() {
   279  		path := p.(string)
   280  		ac.deleteByFile(path)
   281  		if a := readAccount(path); a != nil {
   282  			ac.add(*a)
   283  		}
   284  	}
   285  	end := time.Now()
   286  
   287  	select {
   288  	case ac.notify <- struct{}{}:
   289  	default:
   290  	}
   291  	log.Trace("Handled keystore changes", "time", end.Sub(start))
   292  	return nil
   293  }