github.com/cgcardona/r-subnet-evm@v0.1.5/accounts/keystore/account_cache_test.go (about)

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