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