github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/account_cache.go (about) 1 // Copyright 2017 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core 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 "sort" 26 "strings" 27 "sync" 28 "time" 29 30 mapset "github.com/deckarep/golang-set" 31 32 "github.com/core-coin/go-core/v2/accounts" 33 "github.com/core-coin/go-core/v2/common" 34 "github.com/core-coin/go-core/v2/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 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 func newAccountCache(keydir string) (*accountCache, chan struct{}) { 79 ac := &accountCache{ 80 keydir: keydir, 81 byAddr: make(map[common.Address][]accounts.Account), 82 notify: make(chan struct{}, 1), 83 fileC: fileCache{all: mapset.NewThreadUnsafeSet()}, 84 } 85 ac.watcher = newWatcher(ac) 86 return ac, ac.notify 87 } 88 89 func (ac *accountCache) accounts() []accounts.Account { 90 ac.maybeReload() 91 ac.mu.Lock() 92 defer ac.mu.Unlock() 93 cpy := make([]accounts.Account, len(ac.all)) 94 copy(cpy, ac.all) 95 return cpy 96 } 97 98 func (ac *accountCache) hasAddress(addr common.Address) bool { 99 ac.maybeReload() 100 ac.mu.Lock() 101 defer ac.mu.Unlock() 102 return len(ac.byAddr[addr]) > 0 103 } 104 105 func (ac *accountCache) add(newAccount accounts.Account) { 106 ac.mu.Lock() 107 defer ac.mu.Unlock() 108 109 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 110 if i < len(ac.all) && ac.all[i] == newAccount { 111 return 112 } 113 // newAccount is not in the cache. 114 ac.all = append(ac.all, accounts.Account{}) 115 copy(ac.all[i+1:], ac.all[i:]) 116 ac.all[i] = newAccount 117 ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 118 } 119 120 // note: removed needs to be unique here (i.e. both File and Address must be set). 121 func (ac *accountCache) delete(removed accounts.Account) { 122 ac.mu.Lock() 123 defer ac.mu.Unlock() 124 125 ac.all = removeAccount(ac.all, removed) 126 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 127 delete(ac.byAddr, removed.Address) 128 } else { 129 ac.byAddr[removed.Address] = ba 130 } 131 } 132 133 // deleteByFile removes an account referenced by the given path. 134 func (ac *accountCache) deleteByFile(path string) { 135 ac.mu.Lock() 136 defer ac.mu.Unlock() 137 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) 138 139 if i < len(ac.all) && ac.all[i].URL.Path == path { 140 removed := ac.all[i] 141 ac.all = append(ac.all[:i], ac.all[i+1:]...) 142 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 143 delete(ac.byAddr, removed.Address) 144 } else { 145 ac.byAddr[removed.Address] = ba 146 } 147 } 148 } 149 150 func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { 151 for i := range slice { 152 if slice[i] == elem { 153 return append(slice[:i], slice[i+1:]...) 154 } 155 } 156 return slice 157 } 158 159 // find returns the cached account for address if there is a unique match. 160 // The exact matching rules are explained by the documentation of accounts.Account. 161 // Callers must hold ac.mu. 162 func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { 163 // Limit search to address candidates if possible. 164 matches := ac.all 165 if (a.Address != common.Address{}) { 166 matches = ac.byAddr[a.Address] 167 } 168 if a.URL.Path != "" { 169 // If only the basename is specified, complete the path. 170 if !strings.ContainsRune(a.URL.Path, filepath.Separator) { 171 a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) 172 } 173 for i := range matches { 174 if matches[i].URL == a.URL { 175 return matches[i], nil 176 } 177 } 178 if (a.Address == common.Address{}) { 179 return accounts.Account{}, ErrNoMatch 180 } 181 } 182 switch len(matches) { 183 case 1: 184 return matches[0], nil 185 case 0: 186 return accounts.Account{}, ErrNoMatch 187 default: 188 err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} 189 copy(err.Matches, matches) 190 sort.Sort(accountsByURL(err.Matches)) 191 return accounts.Account{}, err 192 } 193 } 194 195 func (ac *accountCache) maybeReload() { 196 ac.mu.Lock() 197 198 if ac.watcher.running { 199 ac.mu.Unlock() 200 return // A watcher is running and will keep the cache up-to-date. 201 } 202 if ac.throttle == nil { 203 ac.throttle = time.NewTimer(0) 204 } else { 205 select { 206 case <-ac.throttle.C: 207 default: 208 ac.mu.Unlock() 209 return // The cache was reloaded recently. 210 } 211 } 212 // No watcher running, start it. 213 ac.watcher.start() 214 ac.throttle.Reset(minReloadInterval) 215 ac.mu.Unlock() 216 ac.scanAccounts() 217 } 218 219 func (ac *accountCache) close() { 220 ac.mu.Lock() 221 ac.watcher.close() 222 if ac.throttle != nil { 223 ac.throttle.Stop() 224 } 225 if ac.notify != nil { 226 close(ac.notify) 227 ac.notify = nil 228 } 229 ac.mu.Unlock() 230 } 231 232 // scanAccounts checks if any changes have occurred on the filesystem, and 233 // updates the account cache accordingly 234 func (ac *accountCache) scanAccounts() error { 235 // Scan the entire folder metadata for file changes 236 creates, deletes, updates, err := ac.fileC.scan(ac.keydir) 237 if err != nil { 238 log.Debug("Failed to reload keystore contents", "err", err) 239 return err 240 } 241 if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { 242 return nil 243 } 244 // Create a helper method to scan the contents of the key files 245 var ( 246 buf = new(bufio.Reader) 247 key struct { 248 Address string `json:"address"` 249 } 250 ) 251 readAccount := func(path string) *accounts.Account { 252 fd, err := os.Open(path) 253 if err != nil { 254 log.Trace("Failed to open keystore file", "path", path, "err", err) 255 return nil 256 } 257 defer fd.Close() 258 buf.Reset(fd) 259 // Parse the address. 260 key.Address = "" 261 err = json.NewDecoder(buf).Decode(&key) 262 addr, AddrErr := common.HexToAddress(key.Address) 263 switch { 264 case AddrErr != nil: 265 log.Debug("Failed to decode keystore key", "path", path, "err", AddrErr) 266 case err != nil: 267 log.Debug("Failed to decode keystore key", "path", path, "err", err) 268 case (addr == common.Address{}): 269 log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") 270 default: 271 return &accounts.Account{ 272 Address: addr, 273 URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, 274 } 275 } 276 return nil 277 } 278 // Process all the file diffs 279 start := time.Now() 280 281 for _, p := range creates.ToSlice() { 282 if a := readAccount(p.(string)); a != nil { 283 ac.add(*a) 284 } 285 } 286 for _, p := range deletes.ToSlice() { 287 ac.deleteByFile(p.(string)) 288 } 289 for _, p := range updates.ToSlice() { 290 path := p.(string) 291 ac.deleteByFile(path) 292 if a := readAccount(path); a != nil { 293 ac.add(*a) 294 } 295 } 296 end := time.Now() 297 298 select { 299 case ac.notify <- struct{}{}: 300 default: 301 } 302 log.Trace("Handled keystore changes", "time", end.Sub(start)) 303 return nil 304 }