gitlab.com/flarenetwork/coreth@v0.1.1/accounts/keystore/keystore_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 "io/ioutil" 31 "math/rand" 32 "os" 33 "runtime" 34 "sort" 35 "strings" 36 "sync" 37 "sync/atomic" 38 "testing" 39 "time" 40 41 "github.com/ethereum/go-ethereum/common" 42 "github.com/ethereum/go-ethereum/crypto" 43 "github.com/ethereum/go-ethereum/event" 44 "gitlab.com/flarenetwork/coreth/accounts" 45 ) 46 47 var testSigData = make([]byte, 32) 48 49 func TestKeyStore(t *testing.T) { 50 dir, ks := tmpKeyStore(t, true) 51 defer os.RemoveAll(dir) 52 53 a, err := ks.NewAccount("foo") 54 if err != nil { 55 t.Fatal(err) 56 } 57 if !strings.HasPrefix(a.URL.Path, dir) { 58 t.Errorf("account file %s doesn't have dir prefix", a.URL) 59 } 60 stat, err := os.Stat(a.URL.Path) 61 if err != nil { 62 t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) 63 } 64 if runtime.GOOS != "windows" && stat.Mode() != 0600 { 65 t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) 66 } 67 if !ks.HasAddress(a.Address) { 68 t.Errorf("HasAccount(%x) should've returned true", a.Address) 69 } 70 if err := ks.Update(a, "foo", "bar"); err != nil { 71 t.Errorf("Update error: %v", err) 72 } 73 if err := ks.Delete(a, "bar"); err != nil { 74 t.Errorf("Delete error: %v", err) 75 } 76 if common.FileExist(a.URL.Path) { 77 t.Errorf("account file %s should be gone after Delete", a.URL) 78 } 79 if ks.HasAddress(a.Address) { 80 t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) 81 } 82 } 83 84 func TestSign(t *testing.T) { 85 dir, ks := tmpKeyStore(t, true) 86 defer os.RemoveAll(dir) 87 88 pass := "" // not used but required by API 89 a1, err := ks.NewAccount(pass) 90 if err != nil { 91 t.Fatal(err) 92 } 93 if err := ks.Unlock(a1, ""); err != nil { 94 t.Fatal(err) 95 } 96 if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { 97 t.Fatal(err) 98 } 99 } 100 101 func TestSignWithPassphrase(t *testing.T) { 102 dir, ks := tmpKeyStore(t, true) 103 defer os.RemoveAll(dir) 104 105 pass := "passwd" 106 acc, err := ks.NewAccount(pass) 107 if err != nil { 108 t.Fatal(err) 109 } 110 111 if _, unlocked := ks.unlocked[acc.Address]; unlocked { 112 t.Fatal("expected account to be locked") 113 } 114 115 _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) 116 if err != nil { 117 t.Fatal(err) 118 } 119 120 if _, unlocked := ks.unlocked[acc.Address]; unlocked { 121 t.Fatal("expected account to be locked") 122 } 123 124 if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { 125 t.Fatal("expected SignHashWithPassphrase to fail with invalid password") 126 } 127 } 128 129 func TestTimedUnlock(t *testing.T) { 130 dir, ks := tmpKeyStore(t, true) 131 defer os.RemoveAll(dir) 132 133 pass := "foo" 134 a1, err := ks.NewAccount(pass) 135 if err != nil { 136 t.Fatal(err) 137 } 138 139 // Signing without passphrase fails because account is locked 140 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 141 if err != ErrLocked { 142 t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) 143 } 144 145 // Signing with passphrase works 146 if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { 147 t.Fatal(err) 148 } 149 150 // Signing without passphrase works because account is temp unlocked 151 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 152 if err != nil { 153 t.Fatal("Signing shouldn't return an error after unlocking, got ", err) 154 } 155 156 // Signing fails again after automatic locking 157 time.Sleep(250 * time.Millisecond) 158 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 159 if err != ErrLocked { 160 t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) 161 } 162 } 163 164 func TestOverrideUnlock(t *testing.T) { 165 dir, ks := tmpKeyStore(t, false) 166 defer os.RemoveAll(dir) 167 168 pass := "foo" 169 a1, err := ks.NewAccount(pass) 170 if err != nil { 171 t.Fatal(err) 172 } 173 174 // Unlock indefinitely. 175 if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { 176 t.Fatal(err) 177 } 178 179 // Signing without passphrase works because account is temp unlocked 180 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 181 if err != nil { 182 t.Fatal("Signing shouldn't return an error after unlocking, got ", err) 183 } 184 185 // reset unlock to a shorter period, invalidates the previous unlock 186 if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { 187 t.Fatal(err) 188 } 189 190 // Signing without passphrase still works because account is temp unlocked 191 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 192 if err != nil { 193 t.Fatal("Signing shouldn't return an error after unlocking, got ", err) 194 } 195 196 // Signing fails again after automatic locking 197 time.Sleep(250 * time.Millisecond) 198 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 199 if err != ErrLocked { 200 t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) 201 } 202 } 203 204 // This test should fail under -race if signing races the expiration goroutine. 205 func TestSignRace(t *testing.T) { 206 dir, ks := tmpKeyStore(t, false) 207 defer os.RemoveAll(dir) 208 209 // Create a test account. 210 a1, err := ks.NewAccount("") 211 if err != nil { 212 t.Fatal("could not create the test account", err) 213 } 214 215 if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { 216 t.Fatal("could not unlock the test account", err) 217 } 218 end := time.Now().Add(500 * time.Millisecond) 219 for time.Now().Before(end) { 220 if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { 221 return 222 } else if err != nil { 223 t.Errorf("Sign error: %v", err) 224 return 225 } 226 time.Sleep(1 * time.Millisecond) 227 } 228 t.Errorf("Account did not lock within the timeout") 229 } 230 231 // Tests that the wallet notifier loop starts and stops correctly based on the 232 // addition and removal of wallet event subscriptions. 233 func TestWalletNotifierLifecycle(t *testing.T) { 234 // Create a temporary kesytore to test with 235 dir, ks := tmpKeyStore(t, false) 236 defer os.RemoveAll(dir) 237 238 // Ensure that the notification updater is not running yet 239 time.Sleep(250 * time.Millisecond) 240 ks.mu.RLock() 241 updating := ks.updating 242 ks.mu.RUnlock() 243 244 if updating { 245 t.Errorf("wallet notifier running without subscribers") 246 } 247 // Subscribe to the wallet feed and ensure the updater boots up 248 updates := make(chan accounts.WalletEvent) 249 250 subs := make([]event.Subscription, 2) 251 for i := 0; i < len(subs); i++ { 252 // Create a new subscription 253 subs[i] = ks.Subscribe(updates) 254 255 // Ensure the notifier comes online 256 time.Sleep(250 * time.Millisecond) 257 ks.mu.RLock() 258 updating = ks.updating 259 ks.mu.RUnlock() 260 261 if !updating { 262 t.Errorf("sub %d: wallet notifier not running after subscription", i) 263 } 264 } 265 // Unsubscribe and ensure the updater terminates eventually 266 for i := 0; i < len(subs); i++ { 267 // Close an existing subscription 268 subs[i].Unsubscribe() 269 270 // Ensure the notifier shuts down at and only at the last close 271 for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ { 272 ks.mu.RLock() 273 updating = ks.updating 274 ks.mu.RUnlock() 275 276 if i < len(subs)-1 && !updating { 277 t.Fatalf("sub %d: event notifier stopped prematurely", i) 278 } 279 if i == len(subs)-1 && !updating { 280 return 281 } 282 time.Sleep(250 * time.Millisecond) 283 } 284 } 285 t.Errorf("wallet notifier didn't terminate after unsubscribe") 286 } 287 288 type walletEvent struct { 289 accounts.WalletEvent 290 a accounts.Account 291 } 292 293 // Tests that wallet notifications and correctly fired when accounts are added 294 // or deleted from the keystore. 295 func TestWalletNotifications(t *testing.T) { 296 dir, ks := tmpKeyStore(t, false) 297 defer os.RemoveAll(dir) 298 299 // Subscribe to the wallet feed and collect events. 300 var ( 301 events []walletEvent 302 updates = make(chan accounts.WalletEvent) 303 sub = ks.Subscribe(updates) 304 ) 305 defer sub.Unsubscribe() 306 go func() { 307 for { 308 select { 309 case ev := <-updates: 310 events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) 311 case <-sub.Err(): 312 close(updates) 313 return 314 } 315 } 316 }() 317 318 // Randomly add and remove accounts. 319 var ( 320 live = make(map[common.Address]accounts.Account) 321 wantEvents []walletEvent 322 ) 323 for i := 0; i < 1024; i++ { 324 if create := len(live) == 0 || rand.Int()%4 > 0; create { 325 // Add a new account and ensure wallet notifications arrives 326 account, err := ks.NewAccount("") 327 if err != nil { 328 t.Fatalf("failed to create test account: %v", err) 329 } 330 live[account.Address] = account 331 wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account}) 332 } else { 333 // Delete a random account. 334 var account accounts.Account 335 for _, a := range live { 336 account = a 337 break 338 } 339 if err := ks.Delete(account, ""); err != nil { 340 t.Fatalf("failed to delete test account: %v", err) 341 } 342 delete(live, account.Address) 343 wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account}) 344 } 345 } 346 347 // Shut down the event collector and check events. 348 sub.Unsubscribe() 349 for ev := range updates { 350 events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) 351 } 352 checkAccounts(t, live, ks.Wallets()) 353 checkEvents(t, wantEvents, events) 354 } 355 356 // TestImportExport tests the import functionality of a keystore. 357 func TestImportECDSA(t *testing.T) { 358 dir, ks := tmpKeyStore(t, true) 359 defer os.RemoveAll(dir) 360 key, err := crypto.GenerateKey() 361 if err != nil { 362 t.Fatalf("failed to generate key: %v", key) 363 } 364 if _, err = ks.ImportECDSA(key, "old"); err != nil { 365 t.Errorf("importing failed: %v", err) 366 } 367 if _, err = ks.ImportECDSA(key, "old"); err == nil { 368 t.Errorf("importing same key twice succeeded") 369 } 370 if _, err = ks.ImportECDSA(key, "new"); err == nil { 371 t.Errorf("importing same key twice succeeded") 372 } 373 } 374 375 // TestImportECDSA tests the import and export functionality of a keystore. 376 func TestImportExport(t *testing.T) { 377 dir, ks := tmpKeyStore(t, true) 378 defer os.RemoveAll(dir) 379 acc, err := ks.NewAccount("old") 380 if err != nil { 381 t.Fatalf("failed to create account: %v", acc) 382 } 383 json, err := ks.Export(acc, "old", "new") 384 if err != nil { 385 t.Fatalf("failed to export account: %v", acc) 386 } 387 dir2, ks2 := tmpKeyStore(t, true) 388 defer os.RemoveAll(dir2) 389 if _, err = ks2.Import(json, "old", "old"); err == nil { 390 t.Errorf("importing with invalid password succeeded") 391 } 392 acc2, err := ks2.Import(json, "new", "new") 393 if err != nil { 394 t.Errorf("importing failed: %v", err) 395 } 396 if acc.Address != acc2.Address { 397 t.Error("imported account does not match exported account") 398 } 399 if _, err = ks2.Import(json, "new", "new"); err == nil { 400 t.Errorf("importing a key twice succeeded") 401 } 402 403 } 404 405 // TestImportRace tests the keystore on races. 406 // This test should fail under -race if importing races. 407 func TestImportRace(t *testing.T) { 408 dir, ks := tmpKeyStore(t, true) 409 defer os.RemoveAll(dir) 410 acc, err := ks.NewAccount("old") 411 if err != nil { 412 t.Fatalf("failed to create account: %v", acc) 413 } 414 json, err := ks.Export(acc, "old", "new") 415 if err != nil { 416 t.Fatalf("failed to export account: %v", acc) 417 } 418 dir2, ks2 := tmpKeyStore(t, true) 419 defer os.RemoveAll(dir2) 420 var atom uint32 421 var wg sync.WaitGroup 422 wg.Add(2) 423 for i := 0; i < 2; i++ { 424 go func() { 425 defer wg.Done() 426 if _, err := ks2.Import(json, "new", "new"); err != nil { 427 atomic.AddUint32(&atom, 1) 428 } 429 430 }() 431 } 432 wg.Wait() 433 if atom != 1 { 434 t.Errorf("Import is racy") 435 } 436 } 437 438 // checkAccounts checks that all known live accounts are present in the wallet list. 439 func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { 440 if len(live) != len(wallets) { 441 t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) 442 return 443 } 444 liveList := make([]accounts.Account, 0, len(live)) 445 for _, account := range live { 446 liveList = append(liveList, account) 447 } 448 sort.Sort(accountsByURL(liveList)) 449 for j, wallet := range wallets { 450 if accs := wallet.Accounts(); len(accs) != 1 { 451 t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) 452 } else if accs[0] != liveList[j] { 453 t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) 454 } 455 } 456 } 457 458 // checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times. 459 func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { 460 for _, wantEv := range want { 461 nmatch := 0 462 for ; len(have) > 0; nmatch++ { 463 if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { 464 break 465 } 466 have = have[1:] 467 } 468 if nmatch == 0 { 469 t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) 470 } 471 } 472 } 473 474 func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { 475 d, err := ioutil.TempDir("", "eth-keystore-test") 476 if err != nil { 477 t.Fatal(err) 478 } 479 newKs := NewPlaintextKeyStore 480 if encrypted { 481 newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } 482 } 483 return d, newKs(d) 484 }