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