github.com/amazechain/amc@v0.1.3/accounts/keystore/account_cache.go (about) 1 // Copyright 2023 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain 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 AmazeChain 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 AmazeChain 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 "github.com/amazechain/amc/accounts" 31 "github.com/amazechain/amc/common/types" 32 "github.com/amazechain/amc/log" 33 mapset "github.com/deckarep/golang-set/v2" 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 = 2 * 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 types.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[types.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 ac := &accountCache{ 79 keydir: keydir, 80 byAddr: make(map[types.Address][]accounts.Account), 81 notify: make(chan struct{}, 1), 82 fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()}, 83 } 84 ac.watcher = newWatcher(ac) 85 return ac, ac.notify 86 } 87 88 func (ac *accountCache) accounts() []accounts.Account { 89 ac.maybeReload() 90 ac.mu.Lock() 91 defer ac.mu.Unlock() 92 cpy := make([]accounts.Account, len(ac.all)) 93 copy(cpy, ac.all) 94 return cpy 95 } 96 97 func (ac *accountCache) hasAddress(addr types.Address) bool { 98 ac.maybeReload() 99 ac.mu.Lock() 100 defer ac.mu.Unlock() 101 return len(ac.byAddr[addr]) > 0 102 } 103 104 func (ac *accountCache) add(newAccount accounts.Account) { 105 ac.mu.Lock() 106 defer ac.mu.Unlock() 107 108 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 109 if i < len(ac.all) && ac.all[i] == newAccount { 110 return 111 } 112 // newAccount is not in the cache. 113 ac.all = append(ac.all, accounts.Account{}) 114 copy(ac.all[i+1:], ac.all[i:]) 115 ac.all[i] = newAccount 116 ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 117 } 118 119 // note: removed needs to be unique here (i.e. both File and Address must be set). 120 func (ac *accountCache) delete(removed accounts.Account) { 121 ac.mu.Lock() 122 defer ac.mu.Unlock() 123 124 ac.all = removeAccount(ac.all, removed) 125 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 126 delete(ac.byAddr, removed.Address) 127 } else { 128 ac.byAddr[removed.Address] = ba 129 } 130 } 131 132 // deleteByFile removes an account referenced by the given path. 133 func (ac *accountCache) deleteByFile(path string) { 134 ac.mu.Lock() 135 defer ac.mu.Unlock() 136 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) 137 138 if i < len(ac.all) && ac.all[i].URL.Path == path { 139 removed := ac.all[i] 140 ac.all = append(ac.all[:i], ac.all[i+1:]...) 141 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 142 delete(ac.byAddr, removed.Address) 143 } else { 144 ac.byAddr[removed.Address] = ba 145 } 146 } 147 } 148 149 // watcherStarted returns true if the watcher loop started running (even if it 150 // has since also ended). 151 func (ac *accountCache) watcherStarted() bool { 152 ac.mu.Lock() 153 defer ac.mu.Unlock() 154 return ac.watcher.running || ac.watcher.runEnded 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 != types.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 == types.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 // scanAccounts checks if any changes have occurred on the filesystem, and 240 // updates the account cache accordingly 241 func (ac *accountCache) scanAccounts() error { 242 // Scan the entire folder metadata for file changes 243 creates, deletes, updates, err := ac.fileC.scan(ac.keydir) 244 if err != nil { 245 log.Debug("Failed to reload keystore contents", "err", err) 246 return err 247 } 248 if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { 249 return nil 250 } 251 // Create a helper method to scan the contents of the key files 252 var ( 253 buf = new(bufio.Reader) 254 key struct { 255 Address string `json:"address"` 256 } 257 ) 258 readAccount := func(path string) *accounts.Account { 259 fd, err := os.Open(path) 260 if err != nil { 261 //log.Trace("Failed to open keystore file", "path", path, "err", err) 262 log.Debug("Failed to open keystore file", "path", path, "err", err) 263 return nil 264 } 265 defer fd.Close() 266 buf.Reset(fd) 267 // Parse the address. 268 key.Address = "" 269 err = json.NewDecoder(buf).Decode(&key) 270 addr := types.HexToAddress(key.Address) 271 switch { 272 case err != nil: 273 log.Debug("Failed to decode keystore key", "path", path, "err", err) 274 case addr == types.Address{}: 275 log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") 276 default: 277 return &accounts.Account{ 278 Address: addr, 279 URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, 280 } 281 } 282 return nil 283 } 284 // Process all the file diffs 285 start := time.Now() 286 287 for _, path := range creates.ToSlice() { 288 if a := readAccount(path); a != nil { 289 ac.add(*a) 290 } 291 } 292 for _, path := range deletes.ToSlice() { 293 ac.deleteByFile(path) 294 } 295 for _, path := range updates.ToSlice() { 296 ac.deleteByFile(path) 297 if a := readAccount(path); a != nil { 298 ac.add(*a) 299 } 300 } 301 end := time.Now() 302 303 select { 304 case ac.notify <- struct{}{}: 305 default: 306 } 307 log.Trace("Handled keystore changes", "time", end.Sub(start)) 308 return nil 309 }