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