github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/chain/accounts/keystore/account_cache_test.go (about) 1 package keystore 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "math/rand" 7 "os" 8 "path/filepath" 9 "reflect" 10 "sort" 11 "testing" 12 "time" 13 14 "github.com/cespare/cp" 15 "github.com/davecgh/go-spew/spew" 16 "github.com/neatlab/neatio/chain/accounts" 17 "github.com/neatlab/neatio/utilities/common" 18 ) 19 20 var ( 21 cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) 22 cachetestAccounts = []accounts.Account{ 23 { 24 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 25 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, 26 }, 27 { 28 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 29 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, 30 }, 31 { 32 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 33 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, 34 }, 35 } 36 ) 37 38 func TestWatchNewFile(t *testing.T) { 39 t.Parallel() 40 41 dir, ks := tmpKeyStore(t, false) 42 defer os.RemoveAll(dir) 43 44 ks.Accounts() 45 time.Sleep(1000 * time.Millisecond) 46 47 wantAccounts := make([]accounts.Account, len(cachetestAccounts)) 48 for i := range cachetestAccounts { 49 wantAccounts[i] = accounts.Account{ 50 Address: cachetestAccounts[i].Address, 51 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))}, 52 } 53 if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil { 54 t.Fatal(err) 55 } 56 } 57 58 var list []accounts.Account 59 for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 { 60 list = ks.Accounts() 61 if reflect.DeepEqual(list, wantAccounts) { 62 63 select { 64 case <-ks.changes: 65 default: 66 t.Fatalf("wasn't notified of new accounts") 67 } 68 return 69 } 70 time.Sleep(d) 71 } 72 t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts)) 73 } 74 75 func TestWatchNoDir(t *testing.T) { 76 t.Parallel() 77 78 rand.Seed(time.Now().UnixNano()) 79 dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 80 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 81 82 list := ks.Accounts() 83 if len(list) > 0 { 84 t.Error("initial account list not empty:", list) 85 } 86 time.Sleep(100 * time.Millisecond) 87 88 os.MkdirAll(dir, 0700) 89 defer os.RemoveAll(dir) 90 file := filepath.Join(dir, "aaa") 91 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 92 t.Fatal(err) 93 } 94 95 wantAccounts := []accounts.Account{cachetestAccounts[0]} 96 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 97 for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { 98 list = ks.Accounts() 99 if reflect.DeepEqual(list, wantAccounts) { 100 101 select { 102 case <-ks.changes: 103 default: 104 t.Fatalf("wasn't notified of new accounts") 105 } 106 return 107 } 108 time.Sleep(d) 109 } 110 t.Errorf("\ngot %v\nwant %v", list, wantAccounts) 111 } 112 113 func TestCacheInitialReload(t *testing.T) { 114 cache, _ := newAccountCache(cachetestDir) 115 accounts := cache.accounts() 116 for _, account := range accounts { 117 fmt.Printf("TestCacheInitialReload account=%v\n", account) 118 } 119 if !reflect.DeepEqual(accounts, cachetestAccounts) { 120 t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) 121 } 122 } 123 124 func TestCacheAddDeleteOrder(t *testing.T) { 125 cache, _ := newAccountCache("testdata/no-such-dir") 126 cache.watcher.running = true 127 128 accs := []accounts.Account{ 129 { 130 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 131 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, 132 }, 133 { 134 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 135 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, 136 }, 137 { 138 Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), 139 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, 140 }, 141 { 142 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 143 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, 144 }, 145 { 146 Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), 147 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, 148 }, 149 { 150 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 151 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, 152 }, 153 { 154 Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), 155 URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, 156 }, 157 } 158 for _, a := range accs { 159 cache.add(a) 160 } 161 162 cache.add(accs[0]) 163 cache.add(accs[2]) 164 165 wantAccounts := make([]accounts.Account, len(accs)) 166 copy(wantAccounts, accs) 167 sort.Sort(accountsByURL(wantAccounts)) 168 list := cache.accounts() 169 if !reflect.DeepEqual(list, wantAccounts) { 170 t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) 171 } 172 for _, a := range accs { 173 if !cache.hasAddress(a.Address) { 174 t.Errorf("expected hasAccount(%x) to return true", a.Address) 175 } 176 } 177 if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { 178 t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) 179 } 180 181 for i := 0; i < len(accs); i += 2 { 182 cache.delete(wantAccounts[i]) 183 } 184 cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) 185 186 wantAccountsAfterDelete := []accounts.Account{ 187 wantAccounts[1], 188 wantAccounts[3], 189 wantAccounts[5], 190 } 191 list = cache.accounts() 192 if !reflect.DeepEqual(list, wantAccountsAfterDelete) { 193 t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) 194 } 195 for _, a := range wantAccountsAfterDelete { 196 if !cache.hasAddress(a.Address) { 197 t.Errorf("expected hasAccount(%x) to return true", a.Address) 198 } 199 } 200 if cache.hasAddress(wantAccounts[0].Address) { 201 t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) 202 } 203 } 204 205 func TestCacheFind(t *testing.T) { 206 dir := filepath.Join("testdata", "dir") 207 cache, _ := newAccountCache(dir) 208 cache.watcher.running = true 209 210 accs := []accounts.Account{ 211 { 212 Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), 213 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, 214 }, 215 { 216 Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), 217 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, 218 }, 219 { 220 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 221 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, 222 }, 223 { 224 Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), 225 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, 226 }, 227 { 228 Address: common.HexToAddress("3Nm7pC9jsTZyLLiTtbX8aRiYWC3HHjW9SU"), 229 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "d.key")}, 230 }, 231 } 232 for _, a := range accs { 233 cache.add(a) 234 } 235 236 nomatchAccount := accounts.Account{ 237 Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), 238 URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, 239 } 240 tests := []struct { 241 Query accounts.Account 242 WantResult accounts.Account 243 WantError error 244 }{ 245 246 {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, 247 248 {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, 249 250 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, 251 252 {Query: accs[0], WantResult: accs[0]}, 253 254 {Query: accs[2], WantResult: accs[2]}, 255 256 { 257 Query: accounts.Account{Address: accs[2].Address}, 258 WantError: &AmbiguousAddrError{ 259 Addr: accs[2].Address, 260 Matches: []accounts.Account{accs[2], accs[3]}, 261 }, 262 }, 263 264 {Query: nomatchAccount, WantError: ErrNoMatch}, 265 {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, 266 {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, 267 {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, 268 } 269 for i, test := range tests { 270 a, err := cache.find(test.Query) 271 if !reflect.DeepEqual(err, test.WantError) { 272 t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) 273 continue 274 } 275 if a != test.WantResult { 276 t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) 277 continue 278 } 279 } 280 } 281 282 func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { 283 var list []accounts.Account 284 for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { 285 list = ks.Accounts() 286 if reflect.DeepEqual(list, wantAccounts) { 287 288 select { 289 case <-ks.changes: 290 default: 291 return fmt.Errorf("wasn't notified of new accounts") 292 } 293 return nil 294 } 295 time.Sleep(d) 296 } 297 return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) 298 } 299 300 func TestUpdatedKeyfileContents(t *testing.T) { 301 t.Parallel() 302 303 rand.Seed(time.Now().UnixNano()) 304 dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 305 ks := NewKeyStore(dir, LightScryptN, LightScryptP) 306 307 list := ks.Accounts() 308 if len(list) > 0 { 309 t.Error("initial account list not empty:", list) 310 } 311 time.Sleep(100 * time.Millisecond) 312 313 os.MkdirAll(dir, 0700) 314 defer os.RemoveAll(dir) 315 file := filepath.Join(dir, "aaa") 316 317 if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { 318 t.Fatal(err) 319 } 320 321 wantAccounts := []accounts.Account{cachetestAccounts[0]} 322 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 323 if err := waitForAccounts(wantAccounts, ks); err != nil { 324 t.Error(err) 325 return 326 } 327 328 time.Sleep(1000 * time.Millisecond) 329 330 if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { 331 t.Fatal(err) 332 return 333 } 334 wantAccounts = []accounts.Account{cachetestAccounts[1]} 335 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 336 if err := waitForAccounts(wantAccounts, ks); err != nil { 337 t.Errorf("First replacement failed") 338 t.Error(err) 339 return 340 } 341 342 time.Sleep(1000 * time.Millisecond) 343 344 if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { 345 t.Fatal(err) 346 return 347 } 348 wantAccounts = []accounts.Account{cachetestAccounts[2]} 349 wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} 350 if err := waitForAccounts(wantAccounts, ks); err != nil { 351 t.Errorf("Second replacement failed") 352 t.Error(err) 353 return 354 } 355 356 time.Sleep(1000 * time.Millisecond) 357 358 if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil { 359 t.Fatal(err) 360 return 361 } 362 if err := waitForAccounts([]accounts.Account{}, ks); err != nil { 363 t.Errorf("Emptying account file failed") 364 t.Error(err) 365 return 366 } 367 } 368 369 func forceCopyFile(dst, src string) error { 370 data, err := ioutil.ReadFile(src) 371 if err != nil { 372 return err 373 } 374 return ioutil.WriteFile(dst, data, 0644) 375 }