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