mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
Merge e798e8ff18 into 38c7eb0f26
This commit is contained in:
commit
35746c15e8
5 changed files with 136 additions and 67 deletions
|
|
@ -228,26 +228,11 @@ func (ac *accountCache) close() {
|
||||||
ac.mu.Unlock()
|
ac.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanAccounts checks if any changes have occurred on the filesystem, and
|
// readAccount is a helper-function to read an encrypted keyfile
|
||||||
// updates the account cache accordingly
|
func readAccount(path string, buf *bufio.Reader) *accounts.Account {
|
||||||
func (ac *accountCache) scanAccounts() error {
|
var key struct {
|
||||||
// Scan the entire folder metadata for file changes
|
|
||||||
creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug("Failed to reload keystore contents", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Create a helper method to scan the contents of the key files
|
|
||||||
var (
|
|
||||||
buf = new(bufio.Reader)
|
|
||||||
key struct {
|
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
}
|
}
|
||||||
)
|
|
||||||
readAccount := func(path string) *accounts.Account {
|
|
||||||
fd, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace("Failed to open keystore file", "path", path, "err", err)
|
log.Trace("Failed to open keystore file", "path", path, "err", err)
|
||||||
|
|
@ -269,11 +254,66 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkFile can be used when a file notification triggered some kind of change on a file
|
||||||
|
// in the keystore directory. The method checks what happened (change/delete/remove/nothing) and
|
||||||
|
// updates the keystore accordingly
|
||||||
|
func (ac *accountCache) checkFile(path string) error {
|
||||||
|
start := time.Now()
|
||||||
|
created, deleted, updated, err := ac.fileC.checkFile(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to reload keystore contents", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !created && !deleted && !updated {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
buf = new(bufio.Reader)
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case created:
|
||||||
|
if a := readAccount(path, buf); a != nil {
|
||||||
|
ac.add(*a)
|
||||||
|
}
|
||||||
|
case deleted:
|
||||||
|
ac.deleteByFile(path)
|
||||||
|
case updated:
|
||||||
|
ac.deleteByFile(path)
|
||||||
|
if a := readAccount(path, buf); a != nil {
|
||||||
|
ac.add(*a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case ac.notify <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
end := time.Now()
|
||||||
|
log.Trace("Handled keystore changes", "time", end.Sub(start))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanAccounts checks if any changes have occurred on the filesystem, and
|
||||||
|
// updates the account cache accordingly
|
||||||
|
func (ac *accountCache) scanAccounts() error {
|
||||||
|
// Scan the entire folder metadata for file changes
|
||||||
|
creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to reload keystore contents", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// Process all the file diffs
|
// Process all the file diffs
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
var (
|
||||||
|
buf = new(bufio.Reader)
|
||||||
|
)
|
||||||
|
|
||||||
for _, p := range creates.List() {
|
for _, p := range creates.List() {
|
||||||
if a := readAccount(p.(string)); a != nil {
|
if a := readAccount(p.(string), buf); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +323,7 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
for _, p := range updates.List() {
|
for _, p := range updates.List() {
|
||||||
path := p.(string)
|
path := p.(string)
|
||||||
ac.deleteByFile(path)
|
ac.deleteByFile(path)
|
||||||
if a := readAccount(path); a != nil {
|
if a := readAccount(path, buf); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,28 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTempDir returns the name of new temporary folder (not created yet)
|
||||||
|
func newTempDir() (string, error) {
|
||||||
|
// On OSX, there's a problem
|
||||||
|
// https://stackoverflow.com/questions/45122459/docker-mounts-denied-the-paths-are-not-shared-from-os-x-and-are-not-known/45123074#45123074
|
||||||
|
//
|
||||||
|
// > /var in macOS is a symbolic link into /private.
|
||||||
|
//
|
||||||
|
// And when creating a tempdir, it returns a path into '/var/folders...'.
|
||||||
|
// However, if we start watching that directory, the notify-events will contain the
|
||||||
|
// canonical paths, and thus e.g. deleted/updated files won't match our existing files.
|
||||||
|
// TLDR; we need to use the canonical path, which we obtain via EvalSymlinks
|
||||||
|
tmpdir, err := filepath.EvalSymlinks(os.TempDir())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(tmpdir, fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())), nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatchNewFile(t *testing.T) {
|
func TestWatchNewFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
@ -93,10 +115,12 @@ func TestWatchNewFile(t *testing.T) {
|
||||||
|
|
||||||
func TestWatchNoDir(t *testing.T) {
|
func TestWatchNoDir(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create ks but not the directory that it watches.
|
// Create ks but not the directory that it watches.
|
||||||
rand.Seed(time.Now().UnixNano())
|
dir, err := newTempDir()
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
|
|
@ -320,9 +344,11 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
func TestUpdatedKeyfileContents(t *testing.T) {
|
func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a temporary kesytore to test with
|
dir, err := newTempDir()
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
|
|
@ -349,8 +375,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
time.Sleep(200 * time.Millisecond)
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
|
|
||||||
// Now replace file contents
|
// Now replace file contents
|
||||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||||
|
|
@ -365,8 +390,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
time.Sleep(200 * time.Millisecond)
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
|
|
||||||
// Now replace file contents again
|
// Now replace file contents again
|
||||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||||
|
|
@ -381,8 +405,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after ioutil.WriteFile
|
time.Sleep(200 * time.Millisecond)
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
|
|
||||||
// Now replace file contents with crap
|
// Now replace file contents with crap
|
||||||
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
|
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,29 @@ type fileCache struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fc *fileCache) checkFile(path string) (created, deleted, updated bool, err error) {
|
||||||
|
fc.mu.Lock()
|
||||||
|
defer fc.mu.Unlock()
|
||||||
|
|
||||||
|
created, deleted, updated, err = false, false, false, nil
|
||||||
|
previouslyKnown := fc.all.Has(path)
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
// A file has been deleted, but it can be a file which we
|
||||||
|
// were not previously watching.
|
||||||
|
deleted = previouslyKnown && os.IsNotExist(err)
|
||||||
|
return created, deleted, updated, err
|
||||||
|
}
|
||||||
|
if skipKeyFile(fi) {
|
||||||
|
log.Trace("Ignoring file on account scan", "path", path)
|
||||||
|
return created, deleted, updated, err
|
||||||
|
}
|
||||||
|
|
||||||
|
created = !previouslyKnown
|
||||||
|
updated = previouslyKnown
|
||||||
|
return created, deleted, updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
// scan performs a new scan on the given directory, compares against the already
|
// scan performs a new scan on the given directory, compares against the already
|
||||||
// cached filenames, and returns file sets: creates, deletes, updates.
|
// cached filenames, and returns file sets: creates, deletes, updates.
|
||||||
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
|
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -379,6 +380,10 @@ func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
d, err = filepath.EvalSymlinks(d)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
new := NewPlaintextKeyStore
|
new := NewPlaintextKeyStore
|
||||||
if encrypted {
|
if encrypted {
|
||||||
new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@
|
||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/rjeczalik/notify"
|
"github.com/rjeczalik/notify"
|
||||||
)
|
)
|
||||||
|
|
@ -77,32 +75,12 @@ func (w *watcher) loop() {
|
||||||
w.running = true
|
w.running = true
|
||||||
w.ac.mu.Unlock()
|
w.ac.mu.Unlock()
|
||||||
|
|
||||||
// Wait for file system events and reload.
|
|
||||||
// When an event occurs, the reload call is delayed a bit so that
|
|
||||||
// multiple events arriving quickly only cause a single reload.
|
|
||||||
var (
|
|
||||||
debounceDuration = 500 * time.Millisecond
|
|
||||||
rescanTriggered = false
|
|
||||||
debounce = time.NewTimer(0)
|
|
||||||
)
|
|
||||||
// Ignore initial trigger
|
|
||||||
if !debounce.Stop() {
|
|
||||||
<-debounce.C
|
|
||||||
}
|
|
||||||
defer debounce.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.quit:
|
case <-w.quit:
|
||||||
return
|
return
|
||||||
case <-w.ev:
|
case ei := <-w.ev:
|
||||||
// Trigger the scan (with delay), if not already triggered
|
w.ac.checkFile(ei.Path())
|
||||||
if !rescanTriggered {
|
|
||||||
debounce.Reset(debounceDuration)
|
|
||||||
rescanTriggered = true
|
|
||||||
}
|
|
||||||
case <-debounce.C:
|
|
||||||
w.ac.scanAccounts()
|
|
||||||
rescanTriggered = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue