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