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