github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/keystore/account_cache_test.go (about)

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