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 }