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