github.com/MetalBlockchain/subnet-evm@v0.4.9/accounts/keystore/account_cache_test.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2017 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package keystore 28 29 import ( 30 "fmt" 31 "math/rand" 32 "os" 33 "path/filepath" 34 "reflect" 35 "sort" 36 "testing" 37 "time" 38 39 "github.com/MetalBlockchain/subnet-evm/accounts" 40 "github.com/cespare/cp" 41 "github.com/davecgh/go-spew/spew" 42 "github.com/ethereum/go-ethereum/common" 43 ) 44 45 var ( 46 cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) 47 cachetestAccounts = []accounts.Account{ 48 { 49 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 50 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, 51 }, 52 { 53 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 54 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, 55 }, 56 { 57 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 58 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, 59 }, 60 } 61 ) 62 63 func TestWatchNewFile(t *testing.T) { 64 t.Skip("FLAKY") 65 t.Parallel() 66 67 dir, ks := tmpKeyStore(t, false) 68 69 // Ensure the watcher is started before adding any files. 70 ks.Accounts() 71 time.Sleep(2000 * time.Millisecond) 72 73 // Move in the files. 74 wantAccounts := make([]accounts.Account, len(cachetestAccounts)) 75 for i := range cachetestAccounts { 76 wantAccounts[i] = accounts.Account{ 77 Address: cachetestAccounts[i].Address, 78 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))}, 79 } 80 if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil { 81 t.Fatal(err) 82 } 83 } 84 85 // ks should see the accounts. 86 var list []accounts.Account 87 for { 88 list = ks.Accounts() 89 if reflect.DeepEqual(list, wantAccounts) { 90 // ks should have also received change notifications 91 select { 92 case <-ks.changes: 93 default: 94 t.Fatalf("wasn't notified of new accounts") 95 } 96 return 97 } 98 time.Sleep(500 * time.Millisecond) 99 } 100 } 101 102 func TestWatchNoDir(t *testing.T) { 103 t.Parallel() 104 105 // Create ks but not the directory that it watches. 106 rand.Seed(time.Now().UnixNano()) 107 dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int())) 108 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 109 110 list := ks.Accounts() 111 if len(list) > 0 { 112 t.Error("initial account list not empty:", list) 113 } 114 time.Sleep(100 * time.Millisecond) 115 116 // Create the directory and copy a key file into it. 117 os.MkdirAll(dir, 0700) 118 defer os.RemoveAll(dir) 119 file := filepath.Join(dir, "aaa") 120 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 121 t.Fatal(err) 122 } 123 124 // ks should see the account. 125 wantAccounts := []accounts.Account{cachetestAccounts[0]} 126 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 127 for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { 128 list = ks.Accounts() 129 if reflect.DeepEqual(list, wantAccounts) { 130 // ks should have also received change notifications 131 select { 132 case <-ks.changes: 133 default: 134 t.Fatalf("wasn't notified of new accounts") 135 } 136 return 137 } 138 time.Sleep(d) 139 } 140 t.Errorf("\ngot %v\nwant %v", list, wantAccounts) 141 } 142 143 func TestCacheInitialReload(t *testing.T) { 144 cache, _ := newAccountCache(cachetestDir) 145 accounts := cache.accounts() 146 if !reflect.DeepEqual(accounts, cachetestAccounts) { 147 t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) 148 } 149 } 150 151 func TestCacheAddDeleteOrder(t *testing.T) { 152 cache, _ := newAccountCache("testdata/no-such-dir") 153 cache.watcher.running = true // prevent unexpected reloads 154 155 accs := []accounts.Account{ 156 { 157 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 158 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, 159 }, 160 { 161 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 162 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, 163 }, 164 { 165 Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), 166 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, 167 }, 168 { 169 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 170 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, 171 }, 172 { 173 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 174 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, 175 }, 176 { 177 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 178 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, 179 }, 180 { 181 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 182 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, 183 }, 184 } 185 for _, a := range accs { 186 cache.add(a) 187 } 188 // Add some of them twice to check that they don't get reinserted. 189 cache.add(accs[0]) 190 cache.add(accs[2]) 191 192 // Check that the account list is sorted by filename. 193 wantAccounts := make([]accounts.Account, len(accs)) 194 copy(wantAccounts, accs) 195 sort.Sort(accountsByURL(wantAccounts)) 196 list := cache.accounts() 197 if !reflect.DeepEqual(list, wantAccounts) { 198 t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) 199 } 200 for _, a := range accs { 201 if !cache.hasAddress(a.Address) { 202 t.Errorf("expected hasAccount(%x) to return true", a.Address) 203 } 204 } 205 if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { 206 t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) 207 } 208 209 // Delete a few keys from the cache. 210 for i := 0; i < len(accs); i += 2 { 211 cache.delete(wantAccounts[i]) 212 } 213 cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) 214 215 // Check content again after deletion. 216 wantAccountsAfterDelete := []accounts.Account{ 217 wantAccounts[1], 218 wantAccounts[3], 219 wantAccounts[5], 220 } 221 list = cache.accounts() 222 if !reflect.DeepEqual(list, wantAccountsAfterDelete) { 223 t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) 224 } 225 for _, a := range wantAccountsAfterDelete { 226 if !cache.hasAddress(a.Address) { 227 t.Errorf("expected hasAccount(%x) to return true", a.Address) 228 } 229 } 230 if cache.hasAddress(wantAccounts[0].Address) { 231 t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) 232 } 233 } 234 235 func TestCacheFind(t *testing.T) { 236 dir := filepath.Join("testdata", "dir") 237 cache, _ := newAccountCache(dir) 238 cache.watcher.running = true // prevent unexpected reloads 239 240 accs := []accounts.Account{ 241 { 242 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 243 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, 244 }, 245 { 246 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 247 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, 248 }, 249 { 250 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 251 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, 252 }, 253 { 254 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 255 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, 256 }, 257 } 258 for _, a := range accs { 259 cache.add(a) 260 } 261 262 nomatchAccount := accounts.Account{ 263 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 264 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, 265 } 266 tests := []struct { 267 Query accounts.Account 268 WantResult accounts.Account 269 WantError error 270 }{ 271 // by address 272 {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, 273 // by file 274 {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, 275 // by basename 276 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, 277 // by file and address 278 {Query: accs[0], WantResult: accs[0]}, 279 // ambiguous address, tie resolved by file 280 {Query: accs[2], WantResult: accs[2]}, 281 // ambiguous address error 282 { 283 Query: accounts.Account{Address: accs[2].Address}, 284 WantError: &AmbiguousAddrError{ 285 Addr: accs[2].Address, 286 Matches: []accounts.Account{accs[2], accs[3]}, 287 }, 288 }, 289 // no match error 290 {Query: nomatchAccount, WantError: ErrNoMatch}, 291 {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, 292 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, 293 {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, 294 } 295 for i, test := range tests { 296 a, err := cache.find(test.Query) 297 if !reflect.DeepEqual(err, test.WantError) { 298 t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) 299 continue 300 } 301 if a != test.WantResult { 302 t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) 303 continue 304 } 305 } 306 } 307 308 func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { 309 var list []accounts.Account 310 for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { 311 list = ks.Accounts() 312 if reflect.DeepEqual(list, wantAccounts) { 313 // ks should have also received change notifications 314 select { 315 case <-ks.changes: 316 default: 317 return fmt.Errorf("wasn't notified of new accounts") 318 } 319 return nil 320 } 321 time.Sleep(d) 322 } 323 return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) 324 } 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.Skip("FLAKY") 330 t.Parallel() 331 332 // Create a temporary keystore to test with 333 rand.Seed(time.Now().UnixNano()) 334 dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-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 os.WriteFile 394 time.Sleep(1000 * time.Millisecond) 395 396 // Now replace file contents with crap 397 if err := os.WriteFile(file, []byte("foo"), 0600); 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 // 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, 0644) 415 }