github.com/ankr-network/stkr-geth-ext@v1.9.7/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/ethereum/go-ethereum/accounts" 33 "github.com/ethereum/go-ethereum/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 if !reflect.DeepEqual(accounts, cachetestAccounts) { 139 t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) 140 } 141 } 142 143 func TestCacheAddDeleteOrder(t *testing.T) { 144 cache, _ := newAccountCache("testdata/no-such-dir") 145 cache.watcher.running = true // prevent unexpected reloads 146 147 accs := []accounts.Account{ 148 { 149 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 150 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, 151 }, 152 { 153 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 154 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, 155 }, 156 { 157 Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), 158 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, 159 }, 160 { 161 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 162 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, 163 }, 164 { 165 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 166 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, 167 }, 168 { 169 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 170 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, 171 }, 172 { 173 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 174 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, 175 }, 176 } 177 for _, a := range accs { 178 cache.add(a) 179 } 180 // Add some of them twice to check that they don't get reinserted. 181 cache.add(accs[0]) 182 cache.add(accs[2]) 183 184 // Check that the account list is sorted by filename. 185 wantAccounts := make([]accounts.Account, len(accs)) 186 copy(wantAccounts, accs) 187 sort.Sort(accountsByURL(wantAccounts)) 188 list := cache.accounts() 189 if !reflect.DeepEqual(list, wantAccounts) { 190 t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) 191 } 192 for _, a := range accs { 193 if !cache.hasAddress(a.Address) { 194 t.Errorf("expected hasAccount(%x) to return true", a.Address) 195 } 196 } 197 if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { 198 t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) 199 } 200 201 // Delete a few keys from the cache. 202 for i := 0; i < len(accs); i += 2 { 203 cache.delete(wantAccounts[i]) 204 } 205 cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) 206 207 // Check content again after deletion. 208 wantAccountsAfterDelete := []accounts.Account{ 209 wantAccounts[1], 210 wantAccounts[3], 211 wantAccounts[5], 212 } 213 list = cache.accounts() 214 if !reflect.DeepEqual(list, wantAccountsAfterDelete) { 215 t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) 216 } 217 for _, a := range wantAccountsAfterDelete { 218 if !cache.hasAddress(a.Address) { 219 t.Errorf("expected hasAccount(%x) to return true", a.Address) 220 } 221 } 222 if cache.hasAddress(wantAccounts[0].Address) { 223 t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) 224 } 225 } 226 227 func TestCacheFind(t *testing.T) { 228 dir := filepath.Join("testdata", "dir") 229 cache, _ := newAccountCache(dir) 230 cache.watcher.running = true // prevent unexpected reloads 231 232 accs := []accounts.Account{ 233 { 234 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 235 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, 236 }, 237 { 238 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 239 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, 240 }, 241 { 242 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 243 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, 244 }, 245 { 246 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 247 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, 248 }, 249 } 250 for _, a := range accs { 251 cache.add(a) 252 } 253 254 nomatchAccount := accounts.Account{ 255 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 256 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, 257 } 258 tests := []struct { 259 Query accounts.Account 260 WantResult accounts.Account 261 WantError error 262 }{ 263 // by address 264 {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, 265 // by file 266 {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, 267 // by basename 268 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, 269 // by file and address 270 {Query: accs[0], WantResult: accs[0]}, 271 // ambiguous address, tie resolved by file 272 {Query: accs[2], WantResult: accs[2]}, 273 // ambiguous address error 274 { 275 Query: accounts.Account{Address: accs[2].Address}, 276 WantError: &AmbiguousAddrError{ 277 Addr: accs[2].Address, 278 Matches: []accounts.Account{accs[2], accs[3]}, 279 }, 280 }, 281 // no match error 282 {Query: nomatchAccount, WantError: ErrNoMatch}, 283 {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, 284 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, 285 {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, 286 } 287 for i, test := range tests { 288 a, err := cache.find(test.Query) 289 if !reflect.DeepEqual(err, test.WantError) { 290 t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) 291 continue 292 } 293 if a != test.WantResult { 294 t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) 295 continue 296 } 297 } 298 } 299 300 func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { 301 var list []accounts.Account 302 for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { 303 list = ks.Accounts() 304 if reflect.DeepEqual(list, wantAccounts) { 305 // ks should have also received change notifications 306 select { 307 case <-ks.changes: 308 default: 309 return fmt.Errorf("wasn't notified of new accounts") 310 } 311 return nil 312 } 313 time.Sleep(d) 314 } 315 return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) 316 } 317 318 // TestUpdatedKeyfileContents tests that updating the contents of a keystore file 319 // is noticed by the watcher, and the account cache is updated accordingly 320 func TestUpdatedKeyfileContents(t *testing.T) { 321 t.Parallel() 322 323 // Create a temporary kesytore to test with 324 rand.Seed(time.Now().UnixNano()) 325 dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 326 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 327 328 list := ks.Accounts() 329 if len(list) > 0 { 330 t.Error("initial account list not empty:", list) 331 } 332 time.Sleep(100 * time.Millisecond) 333 334 // Create the directory and copy a key file into it. 335 os.MkdirAll(dir, 0700) 336 defer os.RemoveAll(dir) 337 file := filepath.Join(dir, "aaa") 338 339 // Place one of our testfiles in there 340 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 341 t.Fatal(err) 342 } 343 344 // ks should see the account. 345 wantAccounts := []accounts.Account{cachetestAccounts[0]} 346 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 347 if err := waitForAccounts(wantAccounts, ks); err != nil { 348 t.Error(err) 349 return 350 } 351 352 // needed so that modTime of `file` is different to its current value after forceCopyFile 353 time.Sleep(1000 * time.Millisecond) 354 355 // Now replace file contents 356 if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { 357 t.Fatal(err) 358 return 359 } 360 wantAccounts = []accounts.Account{cachetestAccounts[1]} 361 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 362 if err := waitForAccounts(wantAccounts, ks); err != nil { 363 t.Errorf("First replacement failed") 364 t.Error(err) 365 return 366 } 367 368 // needed so that modTime of `file` is different to its current value after forceCopyFile 369 time.Sleep(1000 * time.Millisecond) 370 371 // Now replace file contents again 372 if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { 373 t.Fatal(err) 374 return 375 } 376 wantAccounts = []accounts.Account{cachetestAccounts[2]} 377 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 378 if err := waitForAccounts(wantAccounts, ks); err != nil { 379 t.Errorf("Second replacement failed") 380 t.Error(err) 381 return 382 } 383 384 // needed so that modTime of `file` is different to its current value after ioutil.WriteFile 385 time.Sleep(1000 * time.Millisecond) 386 387 // Now replace file contents with crap 388 if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil { 389 t.Fatal(err) 390 return 391 } 392 if err := waitForAccounts([]accounts.Account{}, ks); err != nil { 393 t.Errorf("Emptying account file failed") 394 t.Error(err) 395 return 396 } 397 } 398 399 // forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. 400 func forceCopyFile(dst, src string) error { 401 data, err := ioutil.ReadFile(src) 402 if err != nil { 403 return err 404 } 405 return ioutil.WriteFile(dst, data, 0644) 406 }