account_cache.go 8.1 KB

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