// Copyright 2017 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 . package keystore import ( "fmt" "math/rand" "os" "path/filepath" "reflect" "testing" "time" "github.com/cespare/cp" "github.com/ethereum/go-ethereum/common" "github.com/harmony-one/harmony/accounts" ) var ( cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) cachetestAccounts = []accounts.Account{ { Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, }, { Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, }, { Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, }, } ) func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { var list []accounts.Account for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { list = ks.Accounts() if reflect.DeepEqual(list, wantAccounts) { // ks should have also received change notifications select { case <-ks.changes: default: return fmt.Errorf("wasn't notified of new accounts") } return nil } time.Sleep(d) } return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) } // TestUpdatedKeyfileContents tests that updating the contents of a keystore file // is noticed by the watcher, and the account cache is updated accordingly func TestUpdatedKeyfileContents(t *testing.T) { t.Parallel() // Create a temporary kesytore to test with rand.Seed(time.Now().UnixNano()) dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) ks := NewKeyStore(dir, LightScryptN, LightScryptP) list := ks.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") // Place one of our testfiles in there if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { t.Fatal(err) } // ks should see the account. wantAccounts := []accounts.Account{cachetestAccounts[0]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} if err := waitForAccounts(wantAccounts, ks); err != nil { t.Error(err) return } // needed so that modTime of `file` is different to its current value after forceCopyFile time.Sleep(1000 * time.Millisecond) // Now replace file contents if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { t.Fatal(err) return } wantAccounts = []accounts.Account{cachetestAccounts[1]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} if err := waitForAccounts(wantAccounts, ks); err != nil { t.Errorf("First replacement failed") t.Error(err) return } // needed so that modTime of `file` is different to its current value after forceCopyFile time.Sleep(1000 * time.Millisecond) // Now replace file contents again if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { t.Fatal(err) return } wantAccounts = []accounts.Account{cachetestAccounts[2]} wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} if err := waitForAccounts(wantAccounts, ks); err != nil { t.Errorf("Second replacement failed") t.Error(err) return } // needed so that modTime of `file` is different to its current value after io.WriteFile time.Sleep(1000 * time.Millisecond) // Now replace file contents with crap if err := os.WriteFile(file, []byte("foo"), 0644); err != nil { t.Fatal(err) return } if err := waitForAccounts([]accounts.Account{}, ks); err != nil { t.Errorf("Emptying account file failed") t.Error(err) return } } // forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. func forceCopyFile(dst, src string) error { data, err := os.ReadFile(src) if err != nil { return err } return os.WriteFile(dst, data, 0644) }