github.com/ethereum/go-ethereum@v1.14.3/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  	"slices"
    26  	"sort"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	mapset "github.com/deckarep/golang-set/v2"
    32  	"github.com/ethereum/go-ethereum/accounts"
    33  	"github.com/ethereum/go-ethereum/common"
    34  	"github.com/ethereum/go-ethereum/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  // byURL defines the sorting order for accounts.
    43  func byURL(a, b accounts.Account) int {
    44  	return a.URL.Cmp(b.URL)
    45  }
    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      []accounts.Account
    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: mapset.NewThreadUnsafeSet[string]()},
    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  // watcherStarted returns true if the watcher loop started running (even if it
   150  // has since also ended).
   151  func (ac *accountCache) watcherStarted() bool {
   152  	ac.mu.Lock()
   153  	defer ac.mu.Unlock()
   154  	return ac.watcher.running || ac.watcher.runEnded
   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  		slices.SortFunc(err.Matches, byURL)
   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  // scanAccounts checks if any changes have occurred on the filesystem, and
   240  // updates the account cache accordingly
   241  func (ac *accountCache) scanAccounts() error {
   242  	// Scan the entire folder metadata for file changes
   243  	creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
   244  	if err != nil {
   245  		log.Debug("Failed to reload keystore contents", "err", err)
   246  		return err
   247  	}
   248  	if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
   249  		return nil
   250  	}
   251  	// Create a helper method to scan the contents of the key files
   252  	var (
   253  		buf = new(bufio.Reader)
   254  		key struct {
   255  			Address string `json:"address"`
   256  		}
   257  	)
   258  	readAccount := func(path string) *accounts.Account {
   259  		fd, err := os.Open(path)
   260  		if err != nil {
   261  			log.Trace("Failed to open keystore file", "path", path, "err", err)
   262  			return nil
   263  		}
   264  		defer fd.Close()
   265  		buf.Reset(fd)
   266  		// Parse the address.
   267  		key.Address = ""
   268  		err = json.NewDecoder(buf).Decode(&key)
   269  		addr := common.HexToAddress(key.Address)
   270  		switch {
   271  		case err != nil:
   272  			log.Debug("Failed to decode keystore key", "path", path, "err", err)
   273  		case addr == common.Address{}:
   274  			log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
   275  		default:
   276  			return &accounts.Account{
   277  				Address: addr,
   278  				URL:     accounts.URL{Scheme: KeyStoreScheme, Path: path},
   279  			}
   280  		}
   281  		return nil
   282  	}
   283  	// Process all the file diffs
   284  	start := time.Now()
   285  
   286  	for _, path := range creates.ToSlice() {
   287  		if a := readAccount(path); a != nil {
   288  			ac.add(*a)
   289  		}
   290  	}
   291  	for _, path := range deletes.ToSlice() {
   292  		ac.deleteByFile(path)
   293  	}
   294  	for _, path := range updates.ToSlice() {
   295  		ac.deleteByFile(path)
   296  		if a := readAccount(path); a != nil {
   297  			ac.add(*a)
   298  		}
   299  	}
   300  	end := time.Now()
   301  
   302  	select {
   303  	case ac.notify <- struct{}{}:
   304  	default:
   305  	}
   306  	log.Trace("Handled keystore changes", "time", end.Sub(start))
   307  	return nil
   308  }