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