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