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