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