github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/keystore/account_cache.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package keystore 13 14 import ( 15 "bufio" 16 "encoding/json" 17 "fmt" 18 "os" 19 "path/filepath" 20 "sort" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/Sberex/go-sberex/accounts" 26 "github.com/Sberex/go-sberex/common" 27 "github.com/Sberex/go-sberex/log" 28 "gopkg.in/fatih/set.v0" 29 ) 30 31 // Minimum amount of time between cache reloads. This limit applies if the platform does 32 // not support change notifications. It also applies if the keystore directory does not 33 // exist yet, the code will attempt to create a watcher at most this often. 34 const minReloadInterval = 2 * time.Second 35 36 type accountsByURL []accounts.Account 37 38 func (s accountsByURL) Len() int { return len(s) } 39 func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 } 40 func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 41 42 // AmbiguousAddrError is returned when attempting to unlock 43 // an address for which more than one file exists. 44 type AmbiguousAddrError struct { 45 Addr common.Address 46 Matches []accounts.Account 47 } 48 49 func (err *AmbiguousAddrError) Error() string { 50 files := "" 51 for i, a := range err.Matches { 52 files += a.URL.Path 53 if i < len(err.Matches)-1 { 54 files += ", " 55 } 56 } 57 return fmt.Sprintf("multiple keys match address (%s)", files) 58 } 59 60 // accountCache is a live index of all accounts in the keystore. 61 type accountCache struct { 62 keydir string 63 watcher *watcher 64 mu sync.Mutex 65 all accountsByURL 66 byAddr map[common.Address][]accounts.Account 67 throttle *time.Timer 68 notify chan struct{} 69 fileC fileCache 70 } 71 72 func newAccountCache(keydir string) (*accountCache, chan struct{}) { 73 ac := &accountCache{ 74 keydir: keydir, 75 byAddr: make(map[common.Address][]accounts.Account), 76 notify: make(chan struct{}, 1), 77 fileC: fileCache{all: set.NewNonTS()}, 78 } 79 ac.watcher = newWatcher(ac) 80 return ac, ac.notify 81 } 82 83 func (ac *accountCache) accounts() []accounts.Account { 84 ac.maybeReload() 85 ac.mu.Lock() 86 defer ac.mu.Unlock() 87 cpy := make([]accounts.Account, len(ac.all)) 88 copy(cpy, ac.all) 89 return cpy 90 } 91 92 func (ac *accountCache) hasAddress(addr common.Address) bool { 93 ac.maybeReload() 94 ac.mu.Lock() 95 defer ac.mu.Unlock() 96 return len(ac.byAddr[addr]) > 0 97 } 98 99 func (ac *accountCache) add(newAccount accounts.Account) { 100 ac.mu.Lock() 101 defer ac.mu.Unlock() 102 103 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 104 if i < len(ac.all) && ac.all[i] == newAccount { 105 return 106 } 107 // newAccount is not in the cache. 108 ac.all = append(ac.all, accounts.Account{}) 109 copy(ac.all[i+1:], ac.all[i:]) 110 ac.all[i] = newAccount 111 ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 112 } 113 114 // note: removed needs to be unique here (i.e. both File and Address must be set). 115 func (ac *accountCache) delete(removed accounts.Account) { 116 ac.mu.Lock() 117 defer ac.mu.Unlock() 118 119 ac.all = removeAccount(ac.all, removed) 120 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 121 delete(ac.byAddr, removed.Address) 122 } else { 123 ac.byAddr[removed.Address] = ba 124 } 125 } 126 127 // deleteByFile removes an account referenced by the given path. 128 func (ac *accountCache) deleteByFile(path string) { 129 ac.mu.Lock() 130 defer ac.mu.Unlock() 131 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) 132 133 if i < len(ac.all) && ac.all[i].URL.Path == path { 134 removed := ac.all[i] 135 ac.all = append(ac.all[:i], ac.all[i+1:]...) 136 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 137 delete(ac.byAddr, removed.Address) 138 } else { 139 ac.byAddr[removed.Address] = ba 140 } 141 } 142 } 143 144 func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { 145 for i := range slice { 146 if slice[i] == elem { 147 return append(slice[:i], slice[i+1:]...) 148 } 149 } 150 return slice 151 } 152 153 // find returns the cached account for address if there is a unique match. 154 // The exact matching rules are explained by the documentation of accounts.Account. 155 // Callers must hold ac.mu. 156 func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { 157 // Limit search to address candidates if possible. 158 matches := ac.all 159 if (a.Address != common.Address{}) { 160 matches = ac.byAddr[a.Address] 161 } 162 if a.URL.Path != "" { 163 // If only the basename is specified, complete the path. 164 if !strings.ContainsRune(a.URL.Path, filepath.Separator) { 165 a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) 166 } 167 for i := range matches { 168 if matches[i].URL == a.URL { 169 return matches[i], nil 170 } 171 } 172 if (a.Address == common.Address{}) { 173 return accounts.Account{}, ErrNoMatch 174 } 175 } 176 switch len(matches) { 177 case 1: 178 return matches[0], nil 179 case 0: 180 return accounts.Account{}, ErrNoMatch 181 default: 182 err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} 183 copy(err.Matches, matches) 184 sort.Sort(accountsByURL(err.Matches)) 185 return accounts.Account{}, err 186 } 187 } 188 189 func (ac *accountCache) maybeReload() { 190 ac.mu.Lock() 191 192 if ac.watcher.running { 193 ac.mu.Unlock() 194 return // A watcher is running and will keep the cache up-to-date. 195 } 196 if ac.throttle == nil { 197 ac.throttle = time.NewTimer(0) 198 } else { 199 select { 200 case <-ac.throttle.C: 201 default: 202 ac.mu.Unlock() 203 return // The cache was reloaded recently. 204 } 205 } 206 // No watcher running, start it. 207 ac.watcher.start() 208 ac.throttle.Reset(minReloadInterval) 209 ac.mu.Unlock() 210 ac.scanAccounts() 211 } 212 213 func (ac *accountCache) close() { 214 ac.mu.Lock() 215 ac.watcher.close() 216 if ac.throttle != nil { 217 ac.throttle.Stop() 218 } 219 if ac.notify != nil { 220 close(ac.notify) 221 ac.notify = nil 222 } 223 ac.mu.Unlock() 224 } 225 226 // scanAccounts checks if any changes have occurred on the filesystem, and 227 // updates the account cache accordingly 228 func (ac *accountCache) scanAccounts() error { 229 // Scan the entire folder metadata for file changes 230 creates, deletes, updates, err := ac.fileC.scan(ac.keydir) 231 if err != nil { 232 log.Debug("Failed to reload keystore contents", "err", err) 233 return err 234 } 235 if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 { 236 return nil 237 } 238 // Create a helper method to scan the contents of the key files 239 var ( 240 buf = new(bufio.Reader) 241 key struct { 242 Address string `json:"address"` 243 } 244 ) 245 readAccount := func(path string) *accounts.Account { 246 fd, err := os.Open(path) 247 if err != nil { 248 log.Trace("Failed to open keystore file", "path", path, "err", err) 249 return nil 250 } 251 defer fd.Close() 252 buf.Reset(fd) 253 // Parse the address. 254 key.Address = "" 255 err = json.NewDecoder(buf).Decode(&key) 256 addr := common.HexToAddress(key.Address) 257 switch { 258 case err != nil: 259 log.Debug("Failed to decode keystore key", "path", path, "err", err) 260 case (addr == common.Address{}): 261 log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") 262 default: 263 return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}} 264 } 265 return nil 266 } 267 // Process all the file diffs 268 start := time.Now() 269 270 for _, p := range creates.List() { 271 if a := readAccount(p.(string)); a != nil { 272 ac.add(*a) 273 } 274 } 275 for _, p := range deletes.List() { 276 ac.deleteByFile(p.(string)) 277 } 278 for _, p := range updates.List() { 279 path := p.(string) 280 ac.deleteByFile(path) 281 if a := readAccount(path); a != nil { 282 ac.add(*a) 283 } 284 } 285 end := time.Now() 286 287 select { 288 case ac.notify <- struct{}{}: 289 default: 290 } 291 log.Trace("Handled keystore changes", "time", end.Sub(start)) 292 return nil 293 }