github.com/waltonchain/waltonchain_gwtc_src@v1.1.4-0.20201225072101-8a298c95a819/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-wtc 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-wtc 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/wtc/go-wtc/accounts" 32 "github.com/wtc/go-wtc/common" 33 "github.com/wtc/go-wtc/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 = 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 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 } 75 76 func newAccountCache(keydir string) (*accountCache, chan struct{}) { 77 ac := &accountCache{ 78 keydir: keydir, 79 byAddr: make(map[common.Address][]accounts.Account), 80 notify: make(chan struct{}, 1), 81 } 82 ac.watcher = newWatcher(ac) 83 return ac, ac.notify 84 } 85 86 func (ac *accountCache) accounts() []accounts.Account { 87 ac.maybeReload() 88 ac.mu.Lock() 89 defer ac.mu.Unlock() 90 cpy := make([]accounts.Account, len(ac.all)) 91 copy(cpy, ac.all) 92 return cpy 93 } 94 95 func (ac *accountCache) hasAddress(addr common.Address) bool { 96 ac.maybeReload() 97 ac.mu.Lock() 98 defer ac.mu.Unlock() 99 return len(ac.byAddr[addr]) > 0 100 } 101 102 func (ac *accountCache) add(newAccount accounts.Account) { 103 ac.mu.Lock() 104 defer ac.mu.Unlock() 105 106 i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) 107 if i < len(ac.all) && ac.all[i] == newAccount { 108 return 109 } 110 // newAccount is not in the cache. 111 ac.all = append(ac.all, accounts.Account{}) 112 copy(ac.all[i+1:], ac.all[i:]) 113 ac.all[i] = newAccount 114 ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) 115 } 116 117 // note: removed needs to be unique here (i.e. both File and Address must be set). 118 func (ac *accountCache) delete(removed accounts.Account) { 119 ac.mu.Lock() 120 defer ac.mu.Unlock() 121 122 ac.all = removeAccount(ac.all, removed) 123 if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { 124 delete(ac.byAddr, removed.Address) 125 } else { 126 ac.byAddr[removed.Address] = ba 127 } 128 } 129 130 func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { 131 for i := range slice { 132 if slice[i] == elem { 133 return append(slice[:i], slice[i+1:]...) 134 } 135 } 136 return slice 137 } 138 139 // find returns the cached account for address if there is a unique match. 140 // The exact matching rules are explained by the documentation of accounts.Account. 141 // Callers must hold ac.mu. 142 func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { 143 // Limit search to address candidates if possible. 144 matches := ac.all 145 if (a.Address != common.Address{}) { 146 matches = ac.byAddr[a.Address] 147 } 148 if a.URL.Path != "" { 149 // If only the basename is specified, complete the path. 150 if !strings.ContainsRune(a.URL.Path, filepath.Separator) { 151 a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) 152 } 153 for i := range matches { 154 if matches[i].URL == a.URL { 155 return matches[i], nil 156 } 157 } 158 if (a.Address == common.Address{}) { 159 return accounts.Account{}, ErrNoMatch 160 } 161 } 162 switch len(matches) { 163 case 1: 164 return matches[0], nil 165 case 0: 166 return accounts.Account{}, ErrNoMatch 167 default: 168 err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} 169 copy(err.Matches, matches) 170 return accounts.Account{}, err 171 } 172 } 173 174 func (ac *accountCache) maybeReload() { 175 ac.mu.Lock() 176 defer ac.mu.Unlock() 177 178 if ac.watcher.running { 179 return // A watcher is running and will keep the cache up-to-date. 180 } 181 if ac.throttle == nil { 182 ac.throttle = time.NewTimer(0) 183 } else { 184 select { 185 case <-ac.throttle.C: 186 default: 187 return // The cache was reloaded recently. 188 } 189 } 190 ac.watcher.start() 191 ac.reload() 192 ac.throttle.Reset(minReloadInterval) 193 } 194 195 func (ac *accountCache) close() { 196 ac.mu.Lock() 197 ac.watcher.close() 198 if ac.throttle != nil { 199 ac.throttle.Stop() 200 } 201 if ac.notify != nil { 202 close(ac.notify) 203 ac.notify = nil 204 } 205 ac.mu.Unlock() 206 } 207 208 // reload caches addresses of existing accounts. 209 // Callers must hold ac.mu. 210 func (ac *accountCache) reload() { 211 accounts, err := ac.scan() 212 if err != nil { 213 log.Debug("Failed to reload keystore contents", "err", err) 214 } 215 ac.all = accounts 216 sort.Sort(ac.all) 217 for k := range ac.byAddr { 218 delete(ac.byAddr, k) 219 } 220 for _, a := range accounts { 221 ac.byAddr[a.Address] = append(ac.byAddr[a.Address], a) 222 } 223 select { 224 case ac.notify <- struct{}{}: 225 default: 226 } 227 log.Debug("Reloaded keystore contents", "accounts", len(ac.all)) 228 } 229 230 func (ac *accountCache) scan() ([]accounts.Account, error) { 231 files, err := ioutil.ReadDir(ac.keydir) 232 if err != nil { 233 return nil, err 234 } 235 236 var ( 237 buf = new(bufio.Reader) 238 addrs []accounts.Account 239 keyJSON struct { 240 Address string `json:"address"` 241 } 242 ) 243 for _, fi := range files { 244 path := filepath.Join(ac.keydir, fi.Name()) 245 if skipKeyFile(fi) { 246 log.Trace("Ignoring file on account scan", "path", path) 247 continue 248 } 249 logger := log.New("path", path) 250 251 fd, err := os.Open(path) 252 if err != nil { 253 logger.Trace("Failed to open keystore file", "err", err) 254 continue 255 } 256 buf.Reset(fd) 257 // Parse the address. 258 keyJSON.Address = "" 259 err = json.NewDecoder(buf).Decode(&keyJSON) 260 addr := common.HexToAddress(keyJSON.Address) 261 switch { 262 case err != nil: 263 logger.Debug("Failed to decode keystore key", "err", err) 264 case (addr == common.Address{}): 265 logger.Debug("Failed to decode keystore key", "err", "missing or zero address") 266 default: 267 addrs = append(addrs, accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}) 268 } 269 fd.Close() 270 } 271 return addrs, err 272 } 273 274 func skipKeyFile(fi os.FileInfo) bool { 275 // Skip editor backups and UNIX-style hidden files. 276 if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { 277 return true 278 } 279 // Skip misc special files, directories (yes, symlinks too). 280 if fi.IsDir() || fi.Mode()&os.ModeType != 0 { 281 return true 282 } 283 return false 284 }