284 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			284 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2016 The go-ethereum Authors
							 | 
						||
| 
								 | 
							
								// This file is part of the go-ethereum library.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library is free software: you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
								// it under the terms of the GNU Lesser General Public License as published by
							 | 
						||
| 
								 | 
							
								// the Free Software Foundation, either version 3 of the License, or
							 | 
						||
| 
								 | 
							
								// (at your option) any later version.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library is distributed in the hope that it will be useful,
							 | 
						||
| 
								 | 
							
								// but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
								// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
							 | 
						||
| 
								 | 
							
								// GNU Lesser General Public License for more details.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// You should have received a copy of the GNU Lesser General Public License
							 | 
						||
| 
								 | 
							
								// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package accounts
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"math/rand"
							 | 
						||
| 
								 | 
							
									"os"
							 | 
						||
| 
								 | 
							
									"path/filepath"
							 | 
						||
| 
								 | 
							
									"reflect"
							 | 
						||
| 
								 | 
							
									"sort"
							 | 
						||
| 
								 | 
							
									"testing"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"github.com/cespare/cp"
							 | 
						||
| 
								 | 
							
									"github.com/davecgh/go-spew/spew"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/common"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var (
							 | 
						||
| 
								 | 
							
									cachetestDir, _   = filepath.Abs(filepath.Join("testdata", "keystore"))
							 | 
						||
| 
								 | 
							
									cachetestAccounts = []Account{
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(cachetestDir, "aaa"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(cachetestDir, "zzz"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestWatchNewFile(t *testing.T) {
							 | 
						||
| 
								 | 
							
									t.Parallel()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									dir, am := tmpManager(t, false)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Ensure the watcher is started before adding any files.
							 | 
						||
| 
								 | 
							
									am.Accounts()
							 | 
						||
| 
								 | 
							
									time.Sleep(200 * time.Millisecond)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Move in the files.
							 | 
						||
| 
								 | 
							
									wantAccounts := make([]Account, len(cachetestAccounts))
							 | 
						||
| 
								 | 
							
									for i := range cachetestAccounts {
							 | 
						||
| 
								 | 
							
										a := cachetestAccounts[i]
							 | 
						||
| 
								 | 
							
										a.File = filepath.Join(dir, filepath.Base(a.File))
							 | 
						||
| 
								 | 
							
										wantAccounts[i] = a
							 | 
						||
| 
								 | 
							
										if err := cp.CopyFile(a.File, cachetestAccounts[i].File); err != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal(err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// am should see the accounts.
							 | 
						||
| 
								 | 
							
									var list []Account
							 | 
						||
| 
								 | 
							
									for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
							 | 
						||
| 
								 | 
							
										list = am.Accounts()
							 | 
						||
| 
								 | 
							
										if reflect.DeepEqual(list, wantAccounts) {
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										time.Sleep(d)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestWatchNoDir(t *testing.T) {
							 | 
						||
| 
								 | 
							
									t.Parallel()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Create am but not the directory that it watches.
							 | 
						||
| 
								 | 
							
									rand.Seed(time.Now().UnixNano())
							 | 
						||
| 
								 | 
							
									dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
							 | 
						||
| 
								 | 
							
									am := NewManager(dir, LightScryptN, LightScryptP)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									list := am.Accounts()
							 | 
						||
| 
								 | 
							
									if len(list) > 0 {
							 | 
						||
| 
								 | 
							
										t.Error("initial account list not empty:", list)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									time.Sleep(100 * time.Millisecond)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Create the directory and copy a key file into it.
							 | 
						||
| 
								 | 
							
									os.MkdirAll(dir, 0700)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
									file := filepath.Join(dir, "aaa")
							 | 
						||
| 
								 | 
							
									if err := cp.CopyFile(file, cachetestAccounts[0].File); err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// am should see the account.
							 | 
						||
| 
								 | 
							
									wantAccounts := []Account{cachetestAccounts[0]}
							 | 
						||
| 
								 | 
							
									wantAccounts[0].File = file
							 | 
						||
| 
								 | 
							
									for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
							 | 
						||
| 
								 | 
							
										list = am.Accounts()
							 | 
						||
| 
								 | 
							
										if reflect.DeepEqual(list, wantAccounts) {
							 | 
						||
| 
								 | 
							
											return
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										time.Sleep(d)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									t.Errorf("\ngot  %v\nwant %v", list, wantAccounts)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestCacheInitialReload(t *testing.T) {
							 | 
						||
| 
								 | 
							
									cache := newAddrCache(cachetestDir)
							 | 
						||
| 
								 | 
							
									accounts := cache.accounts()
							 | 
						||
| 
								 | 
							
									if !reflect.DeepEqual(accounts, cachetestAccounts) {
							 | 
						||
| 
								 | 
							
										t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestCacheAddDeleteOrder(t *testing.T) {
							 | 
						||
| 
								 | 
							
									cache := newAddrCache("testdata/no-such-dir")
							 | 
						||
| 
								 | 
							
									cache.watcher.running = true // prevent unexpected reloads
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									accounts := []Account{
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
							 | 
						||
| 
								 | 
							
											File:    "-309830980",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
							 | 
						||
| 
								 | 
							
											File:    "ggg",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
							 | 
						||
| 
								 | 
							
											File:    "zzzzzz-the-very-last-one.keyXXX",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
							 | 
						||
| 
								 | 
							
											File:    "SOMETHING.key",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
							 | 
						||
| 
								 | 
							
											File:    "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
							 | 
						||
| 
								 | 
							
											File:    "aaa",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
							 | 
						||
| 
								 | 
							
											File:    "zzz",
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range accounts {
							 | 
						||
| 
								 | 
							
										cache.add(a)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									// Add some of them twice to check that they don't get reinserted.
							 | 
						||
| 
								 | 
							
									cache.add(accounts[0])
							 | 
						||
| 
								 | 
							
									cache.add(accounts[2])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check that the account list is sorted by filename.
							 | 
						||
| 
								 | 
							
									wantAccounts := make([]Account, len(accounts))
							 | 
						||
| 
								 | 
							
									copy(wantAccounts, accounts)
							 | 
						||
| 
								 | 
							
									sort.Sort(accountsByFile(wantAccounts))
							 | 
						||
| 
								 | 
							
									list := cache.accounts()
							 | 
						||
| 
								 | 
							
									if !reflect.DeepEqual(list, wantAccounts) {
							 | 
						||
| 
								 | 
							
										t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accounts), spew.Sdump(wantAccounts))
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range accounts {
							 | 
						||
| 
								 | 
							
										if !cache.hasAddress(a.Address) {
							 | 
						||
| 
								 | 
							
											t.Errorf("expected hasAccount(%x) to return true", a.Address)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
							 | 
						||
| 
								 | 
							
										t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Delete a few keys from the cache.
							 | 
						||
| 
								 | 
							
									for i := 0; i < len(accounts); i += 2 {
							 | 
						||
| 
								 | 
							
										cache.delete(wantAccounts[i])
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									cache.delete(Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), File: "something"})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check content again after deletion.
							 | 
						||
| 
								 | 
							
									wantAccountsAfterDelete := []Account{
							 | 
						||
| 
								 | 
							
										wantAccounts[1],
							 | 
						||
| 
								 | 
							
										wantAccounts[3],
							 | 
						||
| 
								 | 
							
										wantAccounts[5],
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									list = cache.accounts()
							 | 
						||
| 
								 | 
							
									if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
							 | 
						||
| 
								 | 
							
										t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range wantAccountsAfterDelete {
							 | 
						||
| 
								 | 
							
										if !cache.hasAddress(a.Address) {
							 | 
						||
| 
								 | 
							
											t.Errorf("expected hasAccount(%x) to return true", a.Address)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if cache.hasAddress(wantAccounts[0].Address) {
							 | 
						||
| 
								 | 
							
										t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestCacheFind(t *testing.T) {
							 | 
						||
| 
								 | 
							
									dir := filepath.Join("testdata", "dir")
							 | 
						||
| 
								 | 
							
									cache := newAddrCache(dir)
							 | 
						||
| 
								 | 
							
									cache.watcher.running = true // prevent unexpected reloads
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									accounts := []Account{
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(dir, "a.key"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(dir, "b.key"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(dir, "c.key"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
							 | 
						||
| 
								 | 
							
											File:    filepath.Join(dir, "c2.key"),
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range accounts {
							 | 
						||
| 
								 | 
							
										cache.add(a)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									nomatchAccount := Account{
							 | 
						||
| 
								 | 
							
										Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
							 | 
						||
| 
								 | 
							
										File:    filepath.Join(dir, "something"),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									tests := []struct {
							 | 
						||
| 
								 | 
							
										Query      Account
							 | 
						||
| 
								 | 
							
										WantResult Account
							 | 
						||
| 
								 | 
							
										WantError  error
							 | 
						||
| 
								 | 
							
									}{
							 | 
						||
| 
								 | 
							
										// by address
							 | 
						||
| 
								 | 
							
										{Query: Account{Address: accounts[0].Address}, WantResult: accounts[0]},
							 | 
						||
| 
								 | 
							
										// by file
							 | 
						||
| 
								 | 
							
										{Query: Account{File: accounts[0].File}, WantResult: accounts[0]},
							 | 
						||
| 
								 | 
							
										// by basename
							 | 
						||
| 
								 | 
							
										{Query: Account{File: filepath.Base(accounts[0].File)}, WantResult: accounts[0]},
							 | 
						||
| 
								 | 
							
										// by file and address
							 | 
						||
| 
								 | 
							
										{Query: accounts[0], WantResult: accounts[0]},
							 | 
						||
| 
								 | 
							
										// ambiguous address, tie resolved by file
							 | 
						||
| 
								 | 
							
										{Query: accounts[2], WantResult: accounts[2]},
							 | 
						||
| 
								 | 
							
										// ambiguous address error
							 | 
						||
| 
								 | 
							
										{
							 | 
						||
| 
								 | 
							
											Query: Account{Address: accounts[2].Address},
							 | 
						||
| 
								 | 
							
											WantError: &AmbiguousAddrError{
							 | 
						||
| 
								 | 
							
												Addr:    accounts[2].Address,
							 | 
						||
| 
								 | 
							
												Matches: []Account{accounts[2], accounts[3]},
							 | 
						||
| 
								 | 
							
											},
							 | 
						||
| 
								 | 
							
										},
							 | 
						||
| 
								 | 
							
										// no match error
							 | 
						||
| 
								 | 
							
										{Query: nomatchAccount, WantError: ErrNoMatch},
							 | 
						||
| 
								 | 
							
										{Query: Account{File: nomatchAccount.File}, WantError: ErrNoMatch},
							 | 
						||
| 
								 | 
							
										{Query: Account{File: filepath.Base(nomatchAccount.File)}, WantError: ErrNoMatch},
							 | 
						||
| 
								 | 
							
										{Query: Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for i, test := range tests {
							 | 
						||
| 
								 | 
							
										a, err := cache.find(test.Query)
							 | 
						||
| 
								 | 
							
										if !reflect.DeepEqual(err, test.WantError) {
							 | 
						||
| 
								 | 
							
											t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
							 | 
						||
| 
								 | 
							
											continue
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if a != test.WantResult {
							 | 
						||
| 
								 | 
							
											t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
							 | 
						||
| 
								 | 
							
											continue
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |