gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/aqua/accounts/keystore/account_cache.go (about)

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