github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/account_cache.go (about)

     1  // Copyright 2017 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package keystore
    18  
    19  import (
    20  	"bufio"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  	"path/filepath"
    25  	"sort"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	mapset "github.com/deckarep/golang-set"
    31  
    32  	"github.com/core-coin/go-core/v2/accounts"
    33  	"github.com/core-coin/go-core/v2/common"
    34  	"github.com/core-coin/go-core/v2/log"
    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  func newAccountCache(keydir string) (*accountCache, chan struct{}) {
    79  	ac := &accountCache{
    80  		keydir: keydir,
    81  		byAddr: make(map[common.Address][]accounts.Account),
    82  		notify: make(chan struct{}, 1),
    83  		fileC:  fileCache{all: mapset.NewThreadUnsafeSet()},
    84  	}
    85  	ac.watcher = newWatcher(ac)
    86  	return ac, ac.notify
    87  }
    88  
    89  func (ac *accountCache) accounts() []accounts.Account {
    90  	ac.maybeReload()
    91  	ac.mu.Lock()
    92  	defer ac.mu.Unlock()
    93  	cpy := make([]accounts.Account, len(ac.all))
    94  	copy(cpy, ac.all)
    95  	return cpy
    96  }
    97  
    98  func (ac *accountCache) hasAddress(addr common.Address) bool {
    99  	ac.maybeReload()
   100  	ac.mu.Lock()
   101  	defer ac.mu.Unlock()
   102  	return len(ac.byAddr[addr]) > 0
   103  }
   104  
   105  func (ac *accountCache) add(newAccount accounts.Account) {
   106  	ac.mu.Lock()
   107  	defer ac.mu.Unlock()
   108  
   109  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
   110  	if i < len(ac.all) && ac.all[i] == newAccount {
   111  		return
   112  	}
   113  	// newAccount is not in the cache.
   114  	ac.all = append(ac.all, accounts.Account{})
   115  	copy(ac.all[i+1:], ac.all[i:])
   116  	ac.all[i] = newAccount
   117  	ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
   118  }
   119  
   120  // note: removed needs to be unique here (i.e. both File and Address must be set).
   121  func (ac *accountCache) delete(removed accounts.Account) {
   122  	ac.mu.Lock()
   123  	defer ac.mu.Unlock()
   124  
   125  	ac.all = removeAccount(ac.all, removed)
   126  	if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   127  		delete(ac.byAddr, removed.Address)
   128  	} else {
   129  		ac.byAddr[removed.Address] = ba
   130  	}
   131  }
   132  
   133  // deleteByFile removes an account referenced by the given path.
   134  func (ac *accountCache) deleteByFile(path string) {
   135  	ac.mu.Lock()
   136  	defer ac.mu.Unlock()
   137  	i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
   138  
   139  	if i < len(ac.all) && ac.all[i].URL.Path == path {
   140  		removed := ac.all[i]
   141  		ac.all = append(ac.all[:i], ac.all[i+1:]...)
   142  		if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
   143  			delete(ac.byAddr, removed.Address)
   144  		} else {
   145  			ac.byAddr[removed.Address] = ba
   146  		}
   147  	}
   148  }
   149  
   150  func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
   151  	for i := range slice {
   152  		if slice[i] == elem {
   153  			return append(slice[:i], slice[i+1:]...)
   154  		}
   155  	}
   156  	return slice
   157  }
   158  
   159  // find returns the cached account for address if there is a unique match.
   160  // The exact matching rules are explained by the documentation of accounts.Account.
   161  // Callers must hold ac.mu.
   162  func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
   163  	// Limit search to address candidates if possible.
   164  	matches := ac.all
   165  	if (a.Address != common.Address{}) {
   166  		matches = ac.byAddr[a.Address]
   167  	}
   168  	if a.URL.Path != "" {
   169  		// If only the basename is specified, complete the path.
   170  		if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
   171  			a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
   172  		}
   173  		for i := range matches {
   174  			if matches[i].URL == a.URL {
   175  				return matches[i], nil
   176  			}
   177  		}
   178  		if (a.Address == common.Address{}) {
   179  			return accounts.Account{}, ErrNoMatch
   180  		}
   181  	}
   182  	switch len(matches) {
   183  	case 1:
   184  		return matches[0], nil
   185  	case 0:
   186  		return accounts.Account{}, ErrNoMatch
   187  	default:
   188  		err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
   189  		copy(err.Matches, matches)
   190  		sort.Sort(accountsByURL(err.Matches))
   191  		return accounts.Account{}, err
   192  	}
   193  }
   194  
   195  func (ac *accountCache) maybeReload() {
   196  	ac.mu.Lock()
   197  
   198  	if ac.watcher.running {
   199  		ac.mu.Unlock()
   200  		return // A watcher is running and will keep the cache up-to-date.
   201  	}
   202  	if ac.throttle == nil {
   203  		ac.throttle = time.NewTimer(0)
   204  	} else {
   205  		select {
   206  		case <-ac.throttle.C:
   207  		default:
   208  			ac.mu.Unlock()
   209  			return // The cache was reloaded recently.
   210  		}
   211  	}
   212  	// No watcher running, start it.
   213  	ac.watcher.start()
   214  	ac.throttle.Reset(minReloadInterval)
   215  	ac.mu.Unlock()
   216  	ac.scanAccounts()
   217  }
   218  
   219  func (ac *accountCache) close() {
   220  	ac.mu.Lock()
   221  	ac.watcher.close()
   222  	if ac.throttle != nil {
   223  		ac.throttle.Stop()
   224  	}
   225  	if ac.notify != nil {
   226  		close(ac.notify)
   227  		ac.notify = nil
   228  	}
   229  	ac.mu.Unlock()
   230  }
   231  
   232  // scanAccounts checks if any changes have occurred on the filesystem, and
   233  // updates the account cache accordingly
   234  func (ac *accountCache) scanAccounts() error {
   235  	// Scan the entire folder metadata for file changes
   236  	creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
   237  	if err != nil {
   238  		log.Debug("Failed to reload keystore contents", "err", err)
   239  		return err
   240  	}
   241  	if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
   242  		return nil
   243  	}
   244  	// Create a helper method to scan the contents of the key files
   245  	var (
   246  		buf = new(bufio.Reader)
   247  		key struct {
   248  			Address string `json:"address"`
   249  		}
   250  	)
   251  	readAccount := func(path string) *accounts.Account {
   252  		fd, err := os.Open(path)
   253  		if err != nil {
   254  			log.Trace("Failed to open keystore file", "path", path, "err", err)
   255  			return nil
   256  		}
   257  		defer fd.Close()
   258  		buf.Reset(fd)
   259  		// Parse the address.
   260  		key.Address = ""
   261  		err = json.NewDecoder(buf).Decode(&key)
   262  		addr, AddrErr := common.HexToAddress(key.Address)
   263  		switch {
   264  		case AddrErr != nil:
   265  			log.Debug("Failed to decode keystore key", "path", path, "err", AddrErr)
   266  		case err != nil:
   267  			log.Debug("Failed to decode keystore key", "path", path, "err", err)
   268  		case (addr == common.Address{}):
   269  			log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
   270  		default:
   271  			return &accounts.Account{
   272  				Address: addr,
   273  				URL:     accounts.URL{Scheme: KeyStoreScheme, Path: path},
   274  			}
   275  		}
   276  		return nil
   277  	}
   278  	// Process all the file diffs
   279  	start := time.Now()
   280  
   281  	for _, p := range creates.ToSlice() {
   282  		if a := readAccount(p.(string)); a != nil {
   283  			ac.add(*a)
   284  		}
   285  	}
   286  	for _, p := range deletes.ToSlice() {
   287  		ac.deleteByFile(p.(string))
   288  	}
   289  	for _, p := range updates.ToSlice() {
   290  		path := p.(string)
   291  		ac.deleteByFile(path)
   292  		if a := readAccount(path); a != nil {
   293  			ac.add(*a)
   294  		}
   295  	}
   296  	end := time.Now()
   297  
   298  	select {
   299  	case ac.notify <- struct{}{}:
   300  	default:
   301  	}
   302  	log.Trace("Handled keystore changes", "time", end.Sub(start))
   303  	return nil
   304  }