github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/accounts/keystore/account_cache_test.go (about)

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