github.com/halybang/go-ethereum@v1.0.5-0.20180325041310-3b262bc1367c/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 "io/ioutil" 24 "os" 25 "path/filepath" 26 "sort" 27 "strings" 28 "sync" 29 "time" 30 31 "github.com/wanchain/go-wanchain/accounts" 32 "github.com/wanchain/go-wanchain/common" 33 "github.com/wanchain/go-wanchain/log" 34 "gopkg.in/fatih/set.v0" 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 type accountsByURL []accounts.Account 43 44 func (s accountsByURL) Len() int { return len(s) } 45 func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 } 46 func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 47 48 // AmbiguousAddrError is returned when attempting to unlock 49 // an address for which more than one file exists. 50 type AmbiguousAddrError struct { 51 Addr common.Address 52 Matches []accounts.Account 53 } 54 55 func (err *AmbiguousAddrError) Error() string { 56 files := "" 57 for i, a := range err.Matches { 58 files += a.URL.Path 59 if i < len(err.Matches)-1 { 60 files += ", " 61 } 62 } 63 return fmt.Sprintf("multiple keys match address (%s)", files) 64 } 65 66 // accountCache is a live index of all accounts in the keystore. 67 type accountCache struct { 68 keydir string 69 watcher *watcher 70 mu sync.Mutex 71 all accountsByURL 72 byAddr map[common.Address][]accounts.Account 73 throttle *time.Timer 74 notify chan struct{} 75 fileC fileCache 76 } 77 78 // fileCache is a cache of files seen during scan of keystore 79 type fileCache struct { 80 all *set.SetNonTS // list of all files 81 mtime time.Time // latest mtime seen 82 mu sync.RWMutex 83 } 84 85 func newAccountCache(keydir string) (*accountCache, chan struct{}) { 86 ac := &accountCache{ 87 keydir: keydir, 88 byAddr: make(map[common.Address][]accounts.Account), 89 notify: make(chan struct{}, 1), 90 fileC: fileCache{all: set.NewNonTS()}, 91 } 92 ac.watcher = newWatcher(ac) 93 return ac, ac.notify 94 } 95 96 func (ac *accountCache) accounts() []accounts.Account { 97 ac.maybeReload() 98 ac.mu.Lock() 99 defer ac.mu.Unlock() 100 cpy := make([]accounts.Account, len(ac.all)) 101 copy(cpy, ac.all) 102 return cpy 103 } 104 105 func (ac *accountCache) hasAddress(addr common.Address) bool { 106 ac.maybeReload() 107 ac.mu.Lock() 108 defer ac.mu.Unlock() 109 return len(ac.byAddr[addr]) > 0 110 } 111 112 func (ac *accountCache) add(newAccount accounts.Account) { 113 ac.mu.Lock() 114 defer ac.mu.Unlock() 115 116 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 117 if i < len(ac.all) && ac.all[i] == newAccount { 118 return 119 } 120 // newAccount is not in the cache. 121 ac.all = append(ac.all, accounts.Account{}) 122 copy(ac.all[i+1:], ac.all[i:]) 123 ac.all[i] = newAccount 124 ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 125 } 126 127 // note: removed needs to be unique here (i.e. both File and Address must be set). 128 func (ac *accountCache) delete(removed accounts.Account) { 129 ac.mu.Lock() 130 defer ac.mu.Unlock() 131 132 ac.all = removeAccount(ac.all, removed) 133 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 134 delete(ac.byAddr, removed.Address) 135 } else { 136 ac.byAddr[removed.Address] = ba 137 } 138 } 139 140 // deleteByFile removes an account referenced by the given path. 141 func (ac *accountCache) deleteByFile(path string) { 142 ac.mu.Lock() 143 defer ac.mu.Unlock() 144 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) 145 146 if i < len(ac.all) && ac.all[i].URL.Path == path { 147 removed := ac.all[i] 148 ac.all = append(ac.all[:i], ac.all[i+1:]...) 149 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 150 delete(ac.byAddr, removed.Address) 151 } else { 152 ac.byAddr[removed.Address] = ba 153 } 154 } 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 sort.Sort(accountsByURL(err.Matches)) 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 // scanFiles performs a new scan on the given directory, compares against the already 240 // cached filenames, and returns file sets: new, missing , modified 241 func (fc *fileCache) scanFiles(keyDir string) (set.Interface, set.Interface, set.Interface, error) { 242 t0 := time.Now() 243 files, err := ioutil.ReadDir(keyDir) 244 t1 := time.Now() 245 if err != nil { 246 return nil, nil, nil, err 247 } 248 fc.mu.RLock() 249 prevMtime := fc.mtime 250 fc.mu.RUnlock() 251 252 filesNow := set.NewNonTS() 253 moddedFiles := set.NewNonTS() 254 var newMtime time.Time 255 for _, fi := range files { 256 modTime := fi.ModTime() 257 path := filepath.Join(keyDir, fi.Name()) 258 if skipKeyFile(fi) { 259 log.Trace("Ignoring file on account scan", "path", path) 260 continue 261 } 262 filesNow.Add(path) 263 if modTime.After(prevMtime) { 264 moddedFiles.Add(path) 265 } 266 if modTime.After(newMtime) { 267 newMtime = modTime 268 } 269 } 270 t2 := time.Now() 271 272 fc.mu.Lock() 273 // Missing = previous - current 274 missing := set.Difference(fc.all, filesNow) 275 // New = current - previous 276 newFiles := set.Difference(filesNow, fc.all) 277 // Modified = modified - new 278 modified := set.Difference(moddedFiles, newFiles) 279 fc.all = filesNow 280 fc.mtime = newMtime 281 fc.mu.Unlock() 282 t3 := time.Now() 283 log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) 284 return newFiles, missing, modified, nil 285 } 286 287 // scanAccounts checks if any changes have occurred on the filesystem, and 288 // updates the account cache accordingly 289 func (ac *accountCache) scanAccounts() error { 290 newFiles, missingFiles, modified, err := ac.fileC.scanFiles(ac.keydir) 291 t1 := time.Now() 292 if err != nil { 293 log.Debug("Failed to reload keystore contents", "err", err) 294 return err 295 } 296 var ( 297 buf = new(bufio.Reader) 298 keyJSON struct { 299 Address string `json:"address"` 300 } 301 ) 302 readAccount := func(path string) *accounts.Account { 303 fd, err := os.Open(path) 304 if err != nil { 305 log.Trace("Failed to open keystore file", "path", path, "err", err) 306 return nil 307 } 308 defer fd.Close() 309 buf.Reset(fd) 310 // Parse the address. 311 keyJSON.Address = "" 312 err = json.NewDecoder(buf).Decode(&keyJSON) 313 addr := common.HexToAddress(keyJSON.Address) 314 switch { 315 case err != nil: 316 log.Debug("Failed to decode keystore key", "path", path, "err", err) 317 case (addr == common.Address{}): 318 log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") 319 default: 320 return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}} 321 } 322 return nil 323 } 324 325 for _, p := range newFiles.List() { 326 path, _ := p.(string) 327 a := readAccount(path) 328 if a != nil { 329 ac.add(*a) 330 } 331 } 332 for _, p := range missingFiles.List() { 333 path, _ := p.(string) 334 ac.deleteByFile(path) 335 } 336 337 for _, p := range modified.List() { 338 path, _ := p.(string) 339 a := readAccount(path) 340 ac.deleteByFile(path) 341 if a != nil { 342 ac.add(*a) 343 } 344 } 345 346 t2 := time.Now() 347 348 select { 349 case ac.notify <- struct{}{}: 350 default: 351 } 352 log.Trace("Handled keystore changes", "time", t2.Sub(t1)) 353 354 return nil 355 } 356 357 func skipKeyFile(fi os.FileInfo) bool { 358 // Skip editor backups and UNIX-style hidden files. 359 if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { 360 return true 361 } 362 // Skip misc special files, directories (yes, symlinks too). 363 if fi.IsDir() || fi.Mode()&os.ModeType != 0 { 364 return true 365 } 366 return false 367 }