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