github.com/core-coin/go-core/v2@v2.1.9/accounts/keystore/account_cache_test.go (about)

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