@@ -31,6 +31,7 @@ import (
31
31
"github.com/ethereum/go-ethereum/accounts"
32
32
"github.com/ethereum/go-ethereum/common"
33
33
"github.com/ethereum/go-ethereum/log"
34
+ "gopkg.in/fatih/set.v0"
34
35
)
35
36
36
37
// Minimum amount of time between cache reloads. This limit applies if the platform does
@@ -71,13 +72,22 @@ type accountCache struct {
71
72
byAddr map [common.Address ][]accounts.Account
72
73
throttle * time.Timer
73
74
notify chan struct {}
75
+ fileC fileCache
76
+ }
77
+
78
+ // fileCache is a cache of files seen during scan of keystore
79
+ type fileCache struct {
80
+ all * set.SetNonTS // list of all files
81
+ mtime time.Time // latest mtime seen
82
+ mu sync.RWMutex
74
83
}
75
84
76
85
func newAccountCache (keydir string ) (* accountCache , chan struct {}) {
77
86
ac := & accountCache {
78
87
keydir : keydir ,
79
88
byAddr : make (map [common.Address ][]accounts.Account ),
80
89
notify : make (chan struct {}, 1 ),
90
+ fileC : fileCache {all : set .NewNonTS ()},
81
91
}
82
92
ac .watcher = newWatcher (ac )
83
93
return ac , ac .notify
@@ -127,6 +137,23 @@ func (ac *accountCache) delete(removed accounts.Account) {
127
137
}
128
138
}
129
139
140
+ // deleteByFile removes an account referenced by the given path.
141
+ func (ac * accountCache ) deleteByFile (path string ) {
142
+ ac .mu .Lock ()
143
+ defer ac .mu .Unlock ()
144
+ i := sort .Search (len (ac .all ), func (i int ) bool { return ac .all [i ].URL .Path >= path })
145
+
146
+ if i < len (ac .all ) && ac .all [i ].URL .Path == path {
147
+ removed := ac .all [i ]
148
+ ac .all = append (ac .all [:i ], ac .all [i + 1 :]... )
149
+ if ba := removeAccount (ac .byAddr [removed .Address ], removed ); len (ba ) == 0 {
150
+ delete (ac .byAddr , removed .Address )
151
+ } else {
152
+ ac .byAddr [removed .Address ] = ba
153
+ }
154
+ }
155
+ }
156
+
130
157
func removeAccount (slice []accounts.Account , elem accounts.Account ) []accounts.Account {
131
158
for i := range slice {
132
159
if slice [i ] == elem {
@@ -167,15 +194,16 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
167
194
default :
168
195
err := & AmbiguousAddrError {Addr : a .Address , Matches : make ([]accounts.Account , len (matches ))}
169
196
copy (err .Matches , matches )
197
+ sort .Sort (accountsByURL (err .Matches ))
170
198
return accounts.Account {}, err
171
199
}
172
200
}
173
201
174
202
func (ac * accountCache ) maybeReload () {
175
203
ac .mu .Lock ()
176
- defer ac .mu .Unlock ()
177
204
178
205
if ac .watcher .running {
206
+ ac .mu .Unlock ()
179
207
return // A watcher is running and will keep the cache up-to-date.
180
208
}
181
209
if ac .throttle == nil {
@@ -184,12 +212,15 @@ func (ac *accountCache) maybeReload() {
184
212
select {
185
213
case <- ac .throttle .C :
186
214
default :
215
+ ac .mu .Unlock ()
187
216
return // The cache was reloaded recently.
188
217
}
189
218
}
219
+ // No watcher running, start it.
190
220
ac .watcher .start ()
191
- ac .reload ()
192
221
ac .throttle .Reset (minReloadInterval )
222
+ ac .mu .Unlock ()
223
+ ac .scanAccounts ()
193
224
}
194
225
195
226
func (ac * accountCache ) close () {
@@ -205,70 +236,122 @@ func (ac *accountCache) close() {
205
236
ac .mu .Unlock ()
206
237
}
207
238
208
- // reload caches addresses of existing accounts.
209
- // Callers must hold ac.mu.
210
- func (ac * accountCache ) reload () {
211
- accounts , err := ac .scan ()
239
+ // scanFiles performs a new scan on the given directory, compares against the already
240
+ // cached filenames, and returns file sets: new, missing , modified
241
+ func (fc * fileCache ) scanFiles (keyDir string ) (set.Interface , set.Interface , set.Interface , error ) {
242
+ t0 := time .Now ()
243
+ files , err := ioutil .ReadDir (keyDir )
244
+ t1 := time .Now ()
212
245
if err != nil {
213
- log . Debug ( "Failed to reload keystore contents" , "err" , err )
246
+ return nil , nil , nil , err
214
247
}
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 :
248
+ fc .mu .RLock ()
249
+ prevMtime := fc .mtime
250
+ fc .mu .RUnlock ()
251
+
252
+ filesNow := set .NewNonTS ()
253
+ moddedFiles := set .NewNonTS ()
254
+ var newMtime time.Time
255
+ for _ , fi := range files {
256
+ modTime := fi .ModTime ()
257
+ path := filepath .Join (keyDir , fi .Name ())
258
+ if skipKeyFile (fi ) {
259
+ log .Trace ("Ignoring file on account scan" , "path" , path )
260
+ continue
261
+ }
262
+ filesNow .Add (path )
263
+ if modTime .After (prevMtime ) {
264
+ moddedFiles .Add (path )
265
+ }
266
+ if modTime .After (newMtime ) {
267
+ newMtime = modTime
268
+ }
226
269
}
227
- log .Debug ("Reloaded keystore contents" , "accounts" , len (ac .all ))
270
+ t2 := time .Now ()
271
+
272
+ fc .mu .Lock ()
273
+ // Missing = previous - current
274
+ missing := set .Difference (fc .all , filesNow )
275
+ // New = current - previous
276
+ newFiles := set .Difference (filesNow , fc .all )
277
+ // Modified = modified - new
278
+ modified := set .Difference (moddedFiles , newFiles )
279
+ fc .all = filesNow
280
+ fc .mtime = newMtime
281
+ fc .mu .Unlock ()
282
+ t3 := time .Now ()
283
+ log .Debug ("FS scan times" , "list" , t1 .Sub (t0 ), "set" , t2 .Sub (t1 ), "diff" , t3 .Sub (t2 ))
284
+ return newFiles , missing , modified , nil
228
285
}
229
286
230
- func (ac * accountCache ) scan () ([]accounts.Account , error ) {
231
- files , err := ioutil .ReadDir (ac .keydir )
287
+ // scanAccounts checks if any changes have occurred on the filesystem, and
288
+ // updates the account cache accordingly
289
+ func (ac * accountCache ) scanAccounts () error {
290
+ newFiles , missingFiles , modified , err := ac .fileC .scanFiles (ac .keydir )
291
+ t1 := time .Now ()
232
292
if err != nil {
233
- return nil , err
293
+ log .Debug ("Failed to reload keystore contents" , "err" , err )
294
+ return err
234
295
}
235
-
236
296
var (
237
297
buf = new (bufio.Reader )
238
- addrs []accounts.Account
239
298
keyJSON struct {
240
299
Address string `json:"address"`
241
300
}
242
301
)
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
-
302
+ readAccount := func (path string ) * accounts.Account {
251
303
fd , err := os .Open (path )
252
304
if err != nil {
253
- logger .Trace ("Failed to open keystore file" , "err" , err )
254
- continue
305
+ log .Trace ("Failed to open keystore file" , "path" , path , "err" , err )
306
+ return nil
255
307
}
308
+ defer fd .Close ()
256
309
buf .Reset (fd )
257
310
// Parse the address.
258
311
keyJSON .Address = ""
259
312
err = json .NewDecoder (buf ).Decode (& keyJSON )
260
313
addr := common .HexToAddress (keyJSON .Address )
261
314
switch {
262
315
case err != nil :
263
- logger .Debug ("Failed to decode keystore key" , "err" , err )
316
+ log .Debug ("Failed to decode keystore key" , "path" , path , "err" , err )
264
317
case (addr == common.Address {}):
265
- logger .Debug ("Failed to decode keystore key" , "err" , "missing or zero address" )
318
+ log .Debug ("Failed to decode keystore key" , "path" , path , "err" , "missing or zero address" )
266
319
default :
267
- addrs = append ( addrs , accounts.Account {Address : addr , URL : accounts.URL {Scheme : KeyStoreScheme , Path : path }})
320
+ return & accounts.Account {Address : addr , URL : accounts.URL {Scheme : KeyStoreScheme , Path : path }}
268
321
}
269
- fd . Close ()
322
+ return nil
270
323
}
271
- return addrs , err
324
+
325
+ for _ , p := range newFiles .List () {
326
+ path , _ := p .(string )
327
+ a := readAccount (path )
328
+ if a != nil {
329
+ ac .add (* a )
330
+ }
331
+ }
332
+ for _ , p := range missingFiles .List () {
333
+ path , _ := p .(string )
334
+ ac .deleteByFile (path )
335
+ }
336
+
337
+ for _ , p := range modified .List () {
338
+ path , _ := p .(string )
339
+ a := readAccount (path )
340
+ ac .deleteByFile (path )
341
+ if a != nil {
342
+ ac .add (* a )
343
+ }
344
+ }
345
+
346
+ t2 := time .Now ()
347
+
348
+ select {
349
+ case ac .notify <- struct {}{}:
350
+ default :
351
+ }
352
+ log .Trace ("Handled keystore changes" , "time" , t2 .Sub (t1 ))
353
+
354
+ return nil
272
355
}
273
356
274
357
func skipKeyFile (fi os.FileInfo ) bool {
0 commit comments