github.com/klaytn/klaytn@v1.10.2/accounts/keystore/account_cache_test.go (about) 1 // Modifications Copyright 2018 The klaytn Authors 2 // Copyright 2017 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 4 // 5 // The go-ethereum library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-ethereum library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 17 // 18 // This file is derived from accounts/keystore/account_cache_test.go (2018/06/04). 19 // Modified and improved for the klaytn development. 20 21 package keystore 22 23 import ( 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "reflect" 29 "sort" 30 "testing" 31 "time" 32 33 "github.com/cespare/cp" 34 "github.com/davecgh/go-spew/spew" 35 "github.com/klaytn/klaytn/accounts" 36 "github.com/klaytn/klaytn/common" 37 "github.com/klaytn/klaytn/log" 38 ) 39 40 var ( 41 cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) 42 cachetestAccounts = []accounts.Account{ 43 { 44 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 45 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, 46 }, 47 { 48 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 49 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, 50 }, 51 { 52 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 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 < 5*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 // TODO-Klaytn Disabled because this test fails intermittently in CI (Issue #833) 99 /* 100 func TestWatchNoDir(t *testing.T) { 101 t.Parallel() 102 103 // Create ks but not the directory that it watches. 104 rand.Seed(time.Now().UnixNano()) 105 dir := filepath.Join(os.TempDir(), fmt.Sprintf("klay-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 106 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 107 108 list := ks.Accounts() 109 if len(list) > 0 { 110 t.Error("initial account list not empty:", list) 111 } 112 time.Sleep(100 * time.Millisecond) 113 114 // Create the directory and copy a key file into it. 115 os.MkdirAll(dir, 0700) 116 defer os.RemoveAll(dir) 117 file := filepath.Join(dir, "aaa") 118 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 119 t.Fatal(err) 120 } 121 122 // ks should see the account. 123 wantAccounts := []accounts.Account{cachetestAccounts[0]} 124 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 125 for d := 200 * time.Millisecond; d < log.StatsReportLimit; d *= 2 { 126 list = ks.Accounts() 127 if reflect.DeepEqual(list, wantAccounts) { 128 // ks should have also received change notifications 129 select { 130 case <-ks.changes: 131 default: 132 t.Fatalf("wasn't notified of new accounts") 133 } 134 return 135 } 136 time.Sleep(d) 137 } 138 t.Errorf("\ngot %v\nwant %v", list, wantAccounts) 139 } 140 */ 141 142 func TestCacheInitialReload(t *testing.T) { 143 cache, _ := newAccountCache(cachetestDir) 144 accounts := cache.accounts() 145 if !reflect.DeepEqual(accounts, cachetestAccounts) { 146 t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) 147 } 148 } 149 150 func TestCacheAddDeleteOrder(t *testing.T) { 151 cache, _ := newAccountCache("testdata/no-such-dir") 152 cache.watcher.running = true // prevent unexpected reloads 153 154 accs := []accounts.Account{ 155 { 156 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 157 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, 158 }, 159 { 160 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 161 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, 162 }, 163 { 164 Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), 165 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, 166 }, 167 { 168 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 169 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, 170 }, 171 { 172 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 173 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, 174 }, 175 { 176 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 177 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, 178 }, 179 { 180 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 181 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, 182 }, 183 } 184 for _, a := range accs { 185 cache.add(a) 186 } 187 // Add some of them twice to check that they don't get reinserted. 188 cache.add(accs[0]) 189 cache.add(accs[2]) 190 191 // Check that the account list is sorted by filename. 192 wantAccounts := make([]accounts.Account, len(accs)) 193 copy(wantAccounts, accs) 194 sort.Sort(accountsByURL(wantAccounts)) 195 list := cache.accounts() 196 if !reflect.DeepEqual(list, wantAccounts) { 197 t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) 198 } 199 for _, a := range accs { 200 if !cache.hasAddress(a.Address) { 201 t.Errorf("expected hasAccount(%x) to return true", a.Address) 202 } 203 } 204 if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { 205 t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) 206 } 207 208 // Delete a few keys from the cache. 209 for i := 0; i < len(accs); i += 2 { 210 cache.delete(wantAccounts[i]) 211 } 212 cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) 213 214 // Check content again after deletion. 215 wantAccountsAfterDelete := []accounts.Account{ 216 wantAccounts[1], 217 wantAccounts[3], 218 wantAccounts[5], 219 } 220 list = cache.accounts() 221 if !reflect.DeepEqual(list, wantAccountsAfterDelete) { 222 t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) 223 } 224 for _, a := range wantAccountsAfterDelete { 225 if !cache.hasAddress(a.Address) { 226 t.Errorf("expected hasAccount(%x) to return true", a.Address) 227 } 228 } 229 if cache.hasAddress(wantAccounts[0].Address) { 230 t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) 231 } 232 } 233 234 func TestCacheFind(t *testing.T) { 235 dir := filepath.Join("testdata", "dir") 236 cache, _ := newAccountCache(dir) 237 cache.watcher.running = true // prevent unexpected reloads 238 239 accs := []accounts.Account{ 240 { 241 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 242 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, 243 }, 244 { 245 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 246 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, 247 }, 248 { 249 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 250 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, 251 }, 252 { 253 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 254 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, 255 }, 256 } 257 for _, a := range accs { 258 cache.add(a) 259 } 260 261 nomatchAccount := accounts.Account{ 262 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 263 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, 264 } 265 tests := []struct { 266 Query accounts.Account 267 WantResult accounts.Account 268 WantError error 269 }{ 270 // by address 271 {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, 272 // by file 273 {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, 274 // by basename 275 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, 276 // by file and address 277 {Query: accs[0], WantResult: accs[0]}, 278 // ambiguous address, tie resolved by file 279 {Query: accs[2], WantResult: accs[2]}, 280 // ambiguous address error 281 { 282 Query: accounts.Account{Address: accs[2].Address}, 283 WantError: &AmbiguousAddrError{ 284 Addr: accs[2].Address, 285 Matches: []accounts.Account{accs[2], accs[3]}, 286 }, 287 }, 288 // no match error 289 {Query: nomatchAccount, WantError: ErrNoMatch}, 290 {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, 291 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, 292 {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, 293 } 294 for i, test := range tests { 295 a, err := cache.find(test.Query) 296 if !reflect.DeepEqual(err, test.WantError) { 297 t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) 298 continue 299 } 300 if a != test.WantResult { 301 t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) 302 continue 303 } 304 } 305 } 306 307 func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { 308 var list []accounts.Account 309 for d := 200 * time.Millisecond; d < log.StatsReportLimit; d *= 2 { 310 list = ks.Accounts() 311 if reflect.DeepEqual(list, wantAccounts) { 312 // ks should have also received change notifications 313 select { 314 case <-ks.changes: 315 default: 316 return fmt.Errorf("wasn't notified of new accounts") 317 } 318 return nil 319 } 320 time.Sleep(d) 321 } 322 return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) 323 } 324 325 // TODO-Klaytn-Issue833 Disabled because this test fails intermittently in CI 326 /* 327 // TestUpdatedKeyfileContents tests that updating the contents of a keystore file 328 // is noticed by the watcher, and the account cache is updated accordingly 329 func TestUpdatedKeyfileContents(t *testing.T) { 330 t.Parallel() 331 332 // Create a temporary kesytore to test with 333 rand.Seed(time.Now().UnixNano()) 334 dir := filepath.Join(os.TempDir(), fmt.Sprintf("klay-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 335 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 336 337 list := ks.Accounts() 338 if len(list) > 0 { 339 t.Error("initial account list not empty:", list) 340 } 341 time.Sleep(100 * time.Millisecond) 342 343 // Create the directory and copy a key file into it. 344 os.MkdirAll(dir, 0700) 345 defer os.RemoveAll(dir) 346 file := filepath.Join(dir, "aaa") 347 348 // Place one of our testfiles in there 349 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 350 t.Fatal(err) 351 } 352 353 // ks should see the account. 354 wantAccounts := []accounts.Account{cachetestAccounts[0]} 355 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 356 if err := waitForAccounts(wantAccounts, ks); err != nil { 357 t.Error(err) 358 return 359 } 360 361 // needed so that modTime of `file` is different to its current value after forceCopyFile 362 time.Sleep(1000 * time.Millisecond) 363 364 // Now replace file contents 365 if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { 366 t.Fatal(err) 367 return 368 } 369 wantAccounts = []accounts.Account{cachetestAccounts[1]} 370 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 371 if err := waitForAccounts(wantAccounts, ks); err != nil { 372 t.Errorf("First replacement failed") 373 t.Error(err) 374 return 375 } 376 377 // needed so that modTime of `file` is different to its current value after forceCopyFile 378 time.Sleep(1000 * time.Millisecond) 379 380 // Now replace file contents again 381 if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { 382 t.Fatal(err) 383 return 384 } 385 wantAccounts = []accounts.Account{cachetestAccounts[2]} 386 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 387 if err := waitForAccounts(wantAccounts, ks); err != nil { 388 t.Errorf("Second replacement failed") 389 t.Error(err) 390 return 391 } 392 393 // needed so that modTime of `file` is different to its current value after ioutil.WriteFile 394 time.Sleep(1000 * time.Millisecond) 395 396 // Now replace file contents with crap 397 if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil { 398 t.Fatal(err) 399 return 400 } 401 if err := waitForAccounts([]accounts.Account{}, ks); err != nil { 402 t.Errorf("Emptying account file failed") 403 t.Error(err) 404 return 405 } 406 } 407 */ 408 409 // forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. 410 func forceCopyFile(dst, src string) error { 411 data, err := ioutil.ReadFile(src) 412 if err != nil { 413 return err 414 } 415 return ioutil.WriteFile(dst, data, 0o644) 416 }