|
|
|
// Copyright 2017 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package keystore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/cespare/cp"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/harmony-one/harmony/accounts"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
|
|
|
|
cachetestAccounts = []accounts.Account{
|
|
|
|
{
|
|
|
|
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
|
|
|
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
|
|
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
|
|
|
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
|
|
|
var list []accounts.Account
|
|
|
|
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
|
|
|
list = ks.Accounts()
|
|
|
|
if reflect.DeepEqual(list, wantAccounts) {
|
|
|
|
// ks should have also received change notifications
|
|
|
|
select {
|
|
|
|
case <-ks.changes:
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("wasn't notified of new accounts")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
time.Sleep(d)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
|
|
|
|
// is noticed by the watcher, and the account cache is updated accordingly
|
|
|
|
func TestUpdatedKeyfileContents(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// Create a temporary kesytore to test with
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
|
|
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
|
|
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
|
|
|
|
|
|
|
list := ks.Accounts()
|
|
|
|
if len(list) > 0 {
|
|
|
|
t.Error("initial account list not empty:", list)
|
|
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
|
|
|
// Create the directory and copy a key file into it.
|
|
|
|
os.MkdirAll(dir, 0700)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
file := filepath.Join(dir, "aaa")
|
|
|
|
|
|
|
|
// Place one of our testfiles in there
|
|
|
|
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ks should see the account.
|
|
|
|
wantAccounts := []accounts.Account{cachetestAccounts[0]}
|
|
|
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
|
|
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
|
|
|
time.Sleep(1000 * time.Millisecond)
|
|
|
|
|
|
|
|
// Now replace file contents
|
|
|
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
wantAccounts = []accounts.Account{cachetestAccounts[1]}
|
|
|
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
|
|
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
|
|
|
t.Errorf("First replacement failed")
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
|
|
|
time.Sleep(1000 * time.Millisecond)
|
|
|
|
|
|
|
|
// Now replace file contents again
|
|
|
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
wantAccounts = []accounts.Account{cachetestAccounts[2]}
|
|
|
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
|
|
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
|
|
|
t.Errorf("Second replacement failed")
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// needed so that modTime of `file` is different to its current value after io.WriteFile
|
|
|
|
time.Sleep(1000 * time.Millisecond)
|
|
|
|
|
|
|
|
// Now replace file contents with crap
|
|
|
|
if err := os.WriteFile(file, []byte("foo"), 0644); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
|
|
|
|
t.Errorf("Emptying account file failed")
|
|
|
|
t.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
|
|
|
|
func forceCopyFile(dst, src string) error {
|
|
|
|
data, err := os.ReadFile(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return os.WriteFile(dst, data, 0644)
|
|
|
|
}
|