github.com/halybang/go-ethereum@v1.0.5-0.20180325041310-3b262bc1367c/accounts/keystore/account_cache_test.go (about)

     1  // Copyright 2018 Wanchain Foundation Ltd
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package keystore
    19  
    20  import (
    21  	"fmt"
    22  	"io/ioutil"
    23  	"math/rand"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"sort"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/cespare/cp"
    32  	"github.com/davecgh/go-spew/spew"
    33  	"github.com/wanchain/go-wanchain/accounts"
    34  	"github.com/wanchain/go-wanchain/common"
    35  )
    36  
    37  var (
    38  	cachetestDir, _   = filepath.Abs(filepath.Join("testdata", "keystore"))
    39  	cachetestAccounts = []accounts.Account{
    40  		{
    41  			Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
    42  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
    43  		},
    44  		{
    45  			Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
    46  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
    47  		},
    48  		{
    49  			Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
    50  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
    51  		},
    52  	}
    53  )
    54  
    55  func TestWatchNewFile(t *testing.T) {
    56  	t.Parallel()
    57  
    58  	dir, ks := tmpKeyStore(t, false)
    59  	defer os.RemoveAll(dir)
    60  
    61  	// Ensure the watcher is started before adding any files.
    62  	ks.Accounts()
    63  	time.Sleep(200 * time.Millisecond)
    64  
    65  	// Move in the files.
    66  	wantAccounts := make([]accounts.Account, len(cachetestAccounts))
    67  	for i := range cachetestAccounts {
    68  		wantAccounts[i] = accounts.Account{
    69  			Address: cachetestAccounts[i].Address,
    70  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
    71  		}
    72  		if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
    73  			t.Fatal(err)
    74  		}
    75  	}
    76  
    77  	// ks should see the accounts.
    78  	var list []accounts.Account
    79  	for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
    80  		list = ks.Accounts()
    81  		if reflect.DeepEqual(list, wantAccounts) {
    82  			// ks should have also received change notifications
    83  			select {
    84  			case <-ks.changes:
    85  			default:
    86  				t.Fatalf("wasn't notified of new accounts")
    87  			}
    88  			return
    89  		}
    90  		time.Sleep(d)
    91  	}
    92  	t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
    93  }
    94  
    95  func TestWatchNoDir(t *testing.T) {
    96  	t.Parallel()
    97  
    98  	// Create ks but not the directory that it watches.
    99  	rand.Seed(time.Now().UnixNano())
   100  	dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
   101  	ks := NewKeyStore(dir, LightScryptN, LightScryptP)
   102  
   103  	list := ks.Accounts()
   104  	if len(list) > 0 {
   105  		t.Error("initial account list not empty:", list)
   106  	}
   107  	time.Sleep(100 * time.Millisecond)
   108  
   109  	// Create the directory and copy a key file into it.
   110  	os.MkdirAll(dir, 0700)
   111  	defer os.RemoveAll(dir)
   112  	file := filepath.Join(dir, "aaa")
   113  	if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	// ks should see the account.
   118  	wantAccounts := []accounts.Account{cachetestAccounts[0]}
   119  	wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
   120  	for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
   121  		list = ks.Accounts()
   122  		if reflect.DeepEqual(list, wantAccounts) {
   123  			// ks should have also received change notifications
   124  			select {
   125  			case <-ks.changes:
   126  			default:
   127  				t.Fatalf("wasn't notified of new accounts")
   128  			}
   129  			return
   130  		}
   131  		time.Sleep(d)
   132  	}
   133  	t.Errorf("\ngot  %v\nwant %v", list, wantAccounts)
   134  }
   135  
   136  func TestCacheInitialReload(t *testing.T) {
   137  	cache, _ := newAccountCache(cachetestDir)
   138  	accounts := cache.accounts()
   139  	if !reflect.DeepEqual(accounts, cachetestAccounts) {
   140  		t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
   141  	}
   142  }
   143  
   144  func TestCacheAddDeleteOrder(t *testing.T) {
   145  	cache, _ := newAccountCache("testdata/no-such-dir")
   146  	cache.watcher.running = true // prevent unexpected reloads
   147  
   148  	accs := []accounts.Account{
   149  		{
   150  			Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
   151  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
   152  		},
   153  		{
   154  			Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
   155  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
   156  		},
   157  		{
   158  			Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
   159  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
   160  		},
   161  		{
   162  			Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
   163  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
   164  		},
   165  		{
   166  			Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
   167  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
   168  		},
   169  		{
   170  			Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
   171  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
   172  		},
   173  		{
   174  			Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
   175  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
   176  		},
   177  	}
   178  	for _, a := range accs {
   179  		cache.add(a)
   180  	}
   181  	// Add some of them twice to check that they don't get reinserted.
   182  	cache.add(accs[0])
   183  	cache.add(accs[2])
   184  
   185  	// Check that the account list is sorted by filename.
   186  	wantAccounts := make([]accounts.Account, len(accs))
   187  	copy(wantAccounts, accs)
   188  	sort.Sort(accountsByURL(wantAccounts))
   189  	list := cache.accounts()
   190  	if !reflect.DeepEqual(list, wantAccounts) {
   191  		t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
   192  	}
   193  	for _, a := range accs {
   194  		if !cache.hasAddress(a.Address) {
   195  			t.Errorf("expected hasAccount(%x) to return true", a.Address)
   196  		}
   197  	}
   198  	if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
   199  		t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
   200  	}
   201  
   202  	// Delete a few keys from the cache.
   203  	for i := 0; i < len(accs); i += 2 {
   204  		cache.delete(wantAccounts[i])
   205  	}
   206  	cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
   207  
   208  	// Check content again after deletion.
   209  	wantAccountsAfterDelete := []accounts.Account{
   210  		wantAccounts[1],
   211  		wantAccounts[3],
   212  		wantAccounts[5],
   213  	}
   214  	list = cache.accounts()
   215  	if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
   216  		t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
   217  	}
   218  	for _, a := range wantAccountsAfterDelete {
   219  		if !cache.hasAddress(a.Address) {
   220  			t.Errorf("expected hasAccount(%x) to return true", a.Address)
   221  		}
   222  	}
   223  	if cache.hasAddress(wantAccounts[0].Address) {
   224  		t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
   225  	}
   226  }
   227  
   228  func TestCacheFind(t *testing.T) {
   229  	dir := filepath.Join("testdata", "dir")
   230  	cache, _ := newAccountCache(dir)
   231  	cache.watcher.running = true // prevent unexpected reloads
   232  
   233  	accs := []accounts.Account{
   234  		{
   235  			Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
   236  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
   237  		},
   238  		{
   239  			Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
   240  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
   241  		},
   242  		{
   243  			Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
   244  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
   245  		},
   246  		{
   247  			Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
   248  			URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
   249  		},
   250  	}
   251  	for _, a := range accs {
   252  		cache.add(a)
   253  	}
   254  
   255  	nomatchAccount := accounts.Account{
   256  		Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
   257  		URL:     accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
   258  	}
   259  	tests := []struct {
   260  		Query      accounts.Account
   261  		WantResult accounts.Account
   262  		WantError  error
   263  	}{
   264  		// by address
   265  		{Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
   266  		// by file
   267  		{Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
   268  		// by basename
   269  		{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
   270  		// by file and address
   271  		{Query: accs[0], WantResult: accs[0]},
   272  		// ambiguous address, tie resolved by file
   273  		{Query: accs[2], WantResult: accs[2]},
   274  		// ambiguous address error
   275  		{
   276  			Query: accounts.Account{Address: accs[2].Address},
   277  			WantError: &AmbiguousAddrError{
   278  				Addr:    accs[2].Address,
   279  				Matches: []accounts.Account{accs[2], accs[3]},
   280  			},
   281  		},
   282  		// no match error
   283  		{Query: nomatchAccount, WantError: ErrNoMatch},
   284  		{Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
   285  		{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
   286  		{Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
   287  	}
   288  	for i, test := range tests {
   289  		a, err := cache.find(test.Query)
   290  		if !reflect.DeepEqual(err, test.WantError) {
   291  			t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
   292  			continue
   293  		}
   294  		if a != test.WantResult {
   295  			t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
   296  			continue
   297  		}
   298  	}
   299  }
   300  
   301  func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
   302  	var list []accounts.Account
   303  	for d := 200 * time.Millisecond; d < 60*time.Second; d *= 2 {
   304  		list = ks.Accounts()
   305  		if reflect.DeepEqual(list, wantAccounts) {
   306  			// ks should have also received change notifications
   307  			select {
   308  			case <-ks.changes:
   309  			default:
   310  				return fmt.Errorf("wasn't notified of new accounts")
   311  			}
   312  			return nil
   313  		}
   314  		time.Sleep(d)
   315  	}
   316  	return fmt.Errorf("\ngot  %v\nwant %v", list, wantAccounts)
   317  }
   318  
   319  // TestUpdatedKeyfileContents tests that updating the contents of a keystore file
   320  // is noticed by the watcher, and the account cache is updated accordingly
   321  func TestUpdatedKeyfileContents(t *testing.T) {
   322  	t.Parallel()
   323  
   324  	// Create a temporary kesytore to test with
   325  	rand.Seed(time.Now().UnixNano())
   326  	dir := filepath.Join(os.TempDir(), fmt.Sprintf("wanchain-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
   327  
   328  	ks := NewKeyStore(dir, LightScryptN, LightScryptP)
   329  	list := ks.Accounts()
   330  	if len(list) > 0 {
   331  		t.Error("initial account list not empty:", list)
   332  	}
   333  	time.Sleep(100 * time.Millisecond)
   334  
   335  	// Create the directory and copy a key file into it.
   336  	os.MkdirAll(dir, 0700)
   337  	defer os.RemoveAll(dir)
   338  	file := filepath.Join(dir, "aaa")
   339  
   340  	// Place one of our testfiles in there
   341  	if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
   342  		t.Fatal(err)
   343  	}
   344  
   345  	// ks should see the account.
   346  	wantAccounts := []accounts.Account{cachetestAccounts[0]}
   347  	wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
   348  	if err := waitForAccounts(wantAccounts, ks); err != nil {
   349  		t.Error(err)
   350  		return
   351  	}
   352  
   353  	// Now replace file contents
   354  	if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
   355  		t.Fatal(err)
   356  		return
   357  	}
   358  	wantAccounts = []accounts.Account{cachetestAccounts[1]}
   359  	wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
   360  	if err := waitForAccounts(wantAccounts, ks); err != nil {
   361  		t.Errorf("First replacement failed")
   362  		t.Error(err)
   363  		return
   364  	}
   365  
   366  	// Now replace file contents again
   367  	if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
   368  		t.Fatal(err)
   369  		return
   370  	}
   371  	wantAccounts = []accounts.Account{cachetestAccounts[2]}
   372  	wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
   373  	if err := waitForAccounts(wantAccounts, ks); err != nil {
   374  		t.Errorf("Second replacement failed")
   375  		t.Error(err)
   376  		return
   377  	}
   378  	// Now replace file contents with crap
   379  	if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
   380  		t.Fatal(err)
   381  		return
   382  	}
   383  	if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
   384  		t.Errorf("Emptying account file failed")
   385  		t.Error(err)
   386  		return
   387  	}
   388  }
   389  
   390  // forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
   391  func forceCopyFile(dst, src string) error {
   392  	data, err := ioutil.ReadFile(src)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	return ioutil.WriteFile(dst, data, 0644)
   397  }