github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/blockchain/pseudohsm/keycache.go (about)

     1  package pseudohsm
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	log "github.com/sirupsen/logrus"
    17  
    18  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    19  )
    20  
    21  // Minimum amount of time between cache reloads. This limit applies if the platform does
    22  // not support change notifications. It also applies if the keystore directory does not
    23  // exist yet, the code will attempt to create a watcher at most this often.
    24  const minReloadInterval = 2 * time.Second
    25  
    26  type keysByFile []XPub
    27  
    28  func (s keysByFile) Len() int           { return len(s) }
    29  func (s keysByFile) Less(i, j int) bool { return s[i].File < s[j].File }
    30  func (s keysByFile) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    31  
    32  // AmbiguousKeyError is returned when attempting to unlock
    33  // an XPub for which more than one file exists.
    34  type AmbiguousKeyError struct {
    35  	Pubkey  string
    36  	Matches []XPub
    37  }
    38  
    39  func (err *AmbiguousKeyError) Error() string {
    40  	files := ""
    41  	for i, a := range err.Matches {
    42  		files += a.File
    43  		if i < len(err.Matches)-1 {
    44  			files += ", "
    45  		}
    46  	}
    47  	return fmt.Sprintf("multiple keys match keys (%s)", files)
    48  }
    49  
    50  // keyCache is a live index of all keys in the keystore.
    51  type keyCache struct {
    52  	keydir   string
    53  	watcher  *watcher
    54  	mu       sync.Mutex
    55  	all      keysByFile
    56  	byPubs   map[chainkd.XPub][]XPub
    57  	throttle *time.Timer
    58  }
    59  
    60  func newKeyCache(keydir string) *keyCache {
    61  	kc := &keyCache{
    62  		keydir: keydir,
    63  		byPubs: make(map[chainkd.XPub][]XPub),
    64  	}
    65  	kc.watcher = newWatcher(kc)
    66  	return kc
    67  }
    68  
    69  func (kc *keyCache) hasKey(xpub chainkd.XPub) bool {
    70  	kc.maybeReload()
    71  	kc.mu.Lock()
    72  	defer kc.mu.Unlock()
    73  	return len(kc.byPubs[xpub]) > 0
    74  }
    75  
    76  func (kc *keyCache) hasAlias(alias string) bool {
    77  	xpubs := kc.keys()
    78  	for _, xpub := range xpubs {
    79  		if xpub.Alias == alias {
    80  			return true
    81  		}
    82  	}
    83  	return false
    84  }
    85  
    86  func (kc *keyCache) add(newKey XPub) {
    87  	kc.mu.Lock()
    88  	defer kc.mu.Unlock()
    89  
    90  	i := sort.Search(len(kc.all), func(i int) bool { return kc.all[i].File >= newKey.File })
    91  	if i < len(kc.all) && kc.all[i] == newKey {
    92  		return
    93  	}
    94  	// newKey is not in the cache.
    95  	kc.all = append(kc.all, XPub{})
    96  	copy(kc.all[i+1:], kc.all[i:])
    97  	kc.all[i] = newKey
    98  	kc.byPubs[newKey.XPub] = append(kc.byPubs[newKey.XPub], newKey)
    99  }
   100  
   101  func (kc *keyCache) keys() []XPub {
   102  	kc.maybeReload()
   103  	kc.mu.Lock()
   104  	defer kc.mu.Unlock()
   105  	cpy := make([]XPub, len(kc.all))
   106  	copy(cpy, kc.all)
   107  	return cpy
   108  }
   109  
   110  func (kc *keyCache) maybeReload() {
   111  	kc.mu.Lock()
   112  	defer kc.mu.Unlock()
   113  
   114  	if kc.watcher.running {
   115  		return // A watcher is running and will keep the cache up-to-date.
   116  	}
   117  
   118  	if kc.throttle == nil {
   119  		kc.throttle = time.NewTimer(0)
   120  	} else {
   121  		select {
   122  		case <-kc.throttle.C:
   123  		default:
   124  			return // The cache was reloaded recently.
   125  		}
   126  	}
   127  	kc.watcher.start()
   128  	kc.reload()
   129  	kc.throttle.Reset(minReloadInterval)
   130  }
   131  
   132  // find returns the cached keys for alias if there is a unique match.
   133  // The exact matching rules are explained by the documentation of Account.
   134  // Callers must hold ac.mu.
   135  func (kc *keyCache) find(xpub XPub) (XPub, error) {
   136  	// Limit search to xpub candidates if possible.
   137  	matches := kc.all
   138  	if (xpub.XPub != chainkd.XPub{}) {
   139  		matches = kc.byPubs[xpub.XPub]
   140  	}
   141  	if xpub.File != "" {
   142  		// If only the basename is specified, complete the path.
   143  		if !strings.ContainsRune(xpub.File, filepath.Separator) {
   144  			xpub.File = filepath.Join(kc.keydir, xpub.File)
   145  		}
   146  		for i := range matches {
   147  			if matches[i].File == xpub.File {
   148  				return matches[i], nil
   149  			}
   150  		}
   151  		if (xpub.XPub == chainkd.XPub{}) {
   152  			return XPub{}, ErrLoadKey
   153  		}
   154  	}
   155  	switch len(matches) {
   156  	case 1:
   157  		return matches[0], nil
   158  	case 0:
   159  		return XPub{}, ErrLoadKey
   160  	default:
   161  		err := &AmbiguousKeyError{Pubkey: hex.EncodeToString(xpub.XPub[:]), Matches: make([]XPub, len(matches))}
   162  		copy(err.Matches, matches)
   163  		return XPub{}, err
   164  	}
   165  }
   166  
   167  // reload caches addresses of existing key.
   168  // Callers must hold ac.mu.
   169  func (kc *keyCache) reload() {
   170  	keys, err := kc.scan()
   171  	if err != nil {
   172  		log.WithFields(log.Fields{"module": logModule, "load keys error": err}).Error("can't load keys")
   173  	}
   174  	kc.all = keys
   175  	sort.Sort(kc.all)
   176  	for k := range kc.byPubs {
   177  		delete(kc.byPubs, k)
   178  	}
   179  	for _, k := range keys {
   180  		kc.byPubs[k.XPub] = append(kc.byPubs[k.XPub], k)
   181  	}
   182  	log.WithFields(log.Fields{"module": logModule, "cache has keys:": len(kc.all)}).Debug("reloaded keys")
   183  }
   184  
   185  func (kc *keyCache) scan() ([]XPub, error) {
   186  	files, err := ioutil.ReadDir(kc.keydir)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	var (
   191  		buf     = new(bufio.Reader)
   192  		keys    []XPub
   193  		keyJSON struct {
   194  			Alias string       `json:"alias"`
   195  			XPub  chainkd.XPub `json:"xpub"`
   196  		}
   197  	)
   198  	for _, fi := range files {
   199  		path := filepath.Join(kc.keydir, fi.Name())
   200  		if skipKeyFile(fi) {
   201  			//log.Printf("ignoring file %v", path)
   202  			//fmt.Printf("ignoring file %v", path)
   203  			continue
   204  		}
   205  		fd, err := os.Open(path)
   206  		if err != nil {
   207  			//log.Printf(err)
   208  			fmt.Printf("err")
   209  			continue
   210  		}
   211  		buf.Reset(fd)
   212  		// Parse the address.
   213  		keyJSON.Alias = ""
   214  		err = json.NewDecoder(buf).Decode(&keyJSON)
   215  		switch {
   216  		case err != nil:
   217  			log.WithFields(log.Fields{"module": logModule, "decode json err": err}).Errorf("can't decode key %s: %v", path, err)
   218  
   219  		case (keyJSON.Alias == ""):
   220  			log.WithFields(log.Fields{"module": logModule, "can't decode key, key path:": path}).Warn("missing or void alias")
   221  		default:
   222  			keys = append(keys, XPub{XPub: keyJSON.XPub, Alias: keyJSON.Alias, File: path})
   223  		}
   224  		fd.Close()
   225  	}
   226  	return keys, err
   227  }
   228  
   229  func (kc *keyCache) delete(removed XPub) {
   230  	kc.mu.Lock()
   231  	defer kc.mu.Unlock()
   232  	kc.all = removeKey(kc.all, removed)
   233  	if ba := removeKey(kc.byPubs[removed.XPub], removed); len(ba) == 0 {
   234  		delete(kc.byPubs, removed.XPub)
   235  	} else {
   236  		kc.byPubs[removed.XPub] = ba
   237  	}
   238  }
   239  
   240  func removeKey(slice []XPub, elem XPub) []XPub {
   241  	for i := range slice {
   242  		if slice[i] == elem {
   243  			return append(slice[:i], slice[i+1:]...)
   244  		}
   245  	}
   246  	return slice
   247  }
   248  
   249  func skipKeyFile(fi os.FileInfo) bool {
   250  	// Skip editor backups and UNIX-style hidden files.
   251  	if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
   252  		return true
   253  	}
   254  	// Skip misc special files, directories (yes, symlinks too).
   255  	if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
   256  		return true
   257  	}
   258  	return false
   259  }
   260  
   261  func (kc *keyCache) close() {
   262  	kc.mu.Lock()
   263  	kc.watcher.close()
   264  	if kc.throttle != nil {
   265  		kc.throttle.Stop()
   266  	}
   267  	kc.mu.Unlock()
   268  }