github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/accounts/keystore/account_cache_test.go (about)

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