github.com/intfoundation/intchain@v0.0.0-20220727031208-4316ad31ca73/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 "fmt" 21 "io/ioutil" 22 "math/rand" 23 "os" 24 "runtime" 25 "sort" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/intfoundation/intchain/accounts" 31 "github.com/intfoundation/intchain/common" 32 "github.com/intfoundation/intchain/event" 33 ) 34 35 var testSigData = make([]byte, 32) 36 37 func TestKeyStore(t *testing.T) { 38 dir, ks := tmpKeyStore(t, true) 39 defer os.RemoveAll(dir) 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 fmt.Print(a.URL.Path) 49 stat, err := os.Stat(a.URL.Path) 50 if err != nil { 51 t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) 52 } 53 if runtime.GOOS != "windows" && stat.Mode() != 0600 { 54 t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) 55 } 56 if !ks.HasAddress(a.Address) { 57 t.Errorf("HasAccount(%x) should've returned true", a.Address) 58 } 59 if err := ks.Update(a, "foo", "bar"); err != nil { 60 t.Errorf("Update error: %v", err) 61 } 62 if err := ks.Delete(a, "bar"); err != nil { 63 t.Errorf("Delete error: %v", err) 64 } 65 if common.FileExist(a.URL.Path) { 66 t.Errorf("account file %s should be gone after Delete", a.URL) 67 } 68 if ks.HasAddress(a.Address) { 69 t.Errorf("HasAccount(%v) should've returned true after Delete", a.Address.String()) 70 } 71 } 72 73 func TestSign(t *testing.T) { 74 dir, ks := tmpKeyStore(t, true) 75 defer os.RemoveAll(dir) 76 77 pass := "" // not used but required by API 78 a1, err := ks.NewAccount(pass) 79 if err != nil { 80 t.Fatal(err) 81 } 82 if err := ks.Unlock(a1, ""); err != nil { 83 t.Fatal(err) 84 } 85 if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { 86 t.Fatal(err) 87 } 88 } 89 90 func TestSignWithPassphrase(t *testing.T) { 91 dir, ks := tmpKeyStore(t, true) 92 defer os.RemoveAll(dir) 93 94 pass := "passwd" 95 acc, err := ks.NewAccount(pass) 96 if err != nil { 97 t.Fatal(err) 98 } 99 100 if _, unlocked := ks.unlocked[acc.Address]; unlocked { 101 t.Fatal("expected account to be locked") 102 } 103 104 _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) 105 if err != nil { 106 t.Fatal(err) 107 } 108 109 if _, unlocked := ks.unlocked[acc.Address]; unlocked { 110 t.Fatal("expected account to be locked") 111 } 112 113 if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { 114 t.Fatal("expected SignHashWithPassphrase to fail with invalid password") 115 } 116 } 117 118 func TestTimedUnlock(t *testing.T) { 119 dir, ks := tmpKeyStore(t, true) 120 defer os.RemoveAll(dir) 121 122 pass := "foo" 123 a1, err := ks.NewAccount(pass) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 // Signing without passphrase fails because account is locked 129 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 130 if err != ErrLocked { 131 t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) 132 } 133 134 // Signing with passphrase works 135 if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { 136 t.Fatal(err) 137 } 138 139 // Signing without passphrase works because account is temp unlocked 140 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 141 if err != nil { 142 t.Fatal("Signing shouldn't return an error after unlocking, got ", err) 143 } 144 145 // Signing fails again after automatic locking 146 time.Sleep(250 * time.Millisecond) 147 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 148 if err != ErrLocked { 149 t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) 150 } 151 } 152 153 func TestOverrideUnlock(t *testing.T) { 154 dir, ks := tmpKeyStore(t, false) 155 defer os.RemoveAll(dir) 156 157 pass := "foo" 158 a1, err := ks.NewAccount(pass) 159 if err != nil { 160 t.Fatal(err) 161 } 162 163 // Unlock indefinitely. 164 if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { 165 t.Fatal(err) 166 } 167 168 // Signing without passphrase works because account is temp unlocked 169 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 170 if err != nil { 171 t.Fatal("Signing shouldn't return an error after unlocking, got ", err) 172 } 173 174 // reset unlock to a shorter period, invalidates the previous unlock 175 if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { 176 t.Fatal(err) 177 } 178 179 // Signing without passphrase still 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 // Signing fails again after automatic locking 186 time.Sleep(250 * time.Millisecond) 187 _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) 188 if err != ErrLocked { 189 t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) 190 } 191 } 192 193 // This test should fail under -race if signing races the expiration goroutine. 194 func TestSignRace(t *testing.T) { 195 dir, ks := tmpKeyStore(t, false) 196 defer os.RemoveAll(dir) 197 198 // Create a test account. 199 a1, err := ks.NewAccount("") 200 if err != nil { 201 t.Fatal("could not create the test account", err) 202 } 203 204 if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { 205 t.Fatal("could not unlock the test account", err) 206 } 207 end := time.Now().Add(500 * time.Millisecond) 208 for time.Now().Before(end) { 209 if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { 210 return 211 } else if err != nil { 212 t.Errorf("Sign error: %v", err) 213 return 214 } 215 time.Sleep(1 * time.Millisecond) 216 } 217 t.Errorf("Account did not lock within the timeout") 218 } 219 220 // Tests that the wallet notifier loop starts and stops correctly based on the 221 // addition and removal of wallet event subscriptions. 222 func TestWalletNotifierLifecycle(t *testing.T) { 223 // Create a temporary kesytore to test with 224 dir, ks := tmpKeyStore(t, false) 225 defer os.RemoveAll(dir) 226 227 // Ensure that the notification updater is not running yet 228 time.Sleep(250 * time.Millisecond) 229 ks.mu.RLock() 230 updating := ks.updating 231 ks.mu.RUnlock() 232 233 if updating { 234 t.Errorf("wallet notifier running without subscribers") 235 } 236 // Subscribe to the wallet feed and ensure the updater boots up 237 updates := make(chan accounts.WalletEvent) 238 239 subs := make([]event.Subscription, 2) 240 for i := 0; i < len(subs); i++ { 241 // Create a new subscription 242 subs[i] = ks.Subscribe(updates) 243 244 // Ensure the notifier comes online 245 time.Sleep(250 * time.Millisecond) 246 ks.mu.RLock() 247 updating = ks.updating 248 ks.mu.RUnlock() 249 250 if !updating { 251 t.Errorf("sub %d: wallet notifier not running after subscription", i) 252 } 253 } 254 // Unsubscribe and ensure the updater terminates eventually 255 for i := 0; i < len(subs); i++ { 256 // Close an existing subscription 257 subs[i].Unsubscribe() 258 259 // Ensure the notifier shuts down at and only at the last close 260 for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ { 261 ks.mu.RLock() 262 updating = ks.updating 263 ks.mu.RUnlock() 264 265 if i < len(subs)-1 && !updating { 266 t.Fatalf("sub %d: event notifier stopped prematurely", i) 267 } 268 if i == len(subs)-1 && !updating { 269 return 270 } 271 time.Sleep(250 * time.Millisecond) 272 } 273 } 274 t.Errorf("wallet notifier didn't terminate after unsubscribe") 275 } 276 277 type walletEvent struct { 278 accounts.WalletEvent 279 a accounts.Account 280 } 281 282 // Tests that wallet notifications and correctly fired when accounts are added 283 // or deleted from the keystore. 284 func TestWalletNotifications(t *testing.T) { 285 dir, ks := tmpKeyStore(t, false) 286 defer os.RemoveAll(dir) 287 288 // Subscribe to the wallet feed and collect events. 289 var ( 290 events []walletEvent 291 updates = make(chan accounts.WalletEvent) 292 sub = ks.Subscribe(updates) 293 ) 294 defer sub.Unsubscribe() 295 go func() { 296 for { 297 select { 298 case ev := <-updates: 299 events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) 300 case <-sub.Err(): 301 close(updates) 302 return 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 sub.Unsubscribe() 338 <-updates 339 checkAccounts(t, live, ks.Wallets()) 340 checkEvents(t, wantEvents, events) 341 } 342 343 // checkAccounts checks that all known live accounts are present in the wallet list. 344 func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { 345 if len(live) != len(wallets) { 346 t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) 347 return 348 } 349 liveList := make([]accounts.Account, 0, len(live)) 350 for _, account := range live { 351 liveList = append(liveList, account) 352 } 353 sort.Sort(accountsByURL(liveList)) 354 for j, wallet := range wallets { 355 if accs := wallet.Accounts(); len(accs) != 1 { 356 t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) 357 } else if accs[0] != liveList[j] { 358 t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) 359 } 360 } 361 } 362 363 // checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times. 364 func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { 365 for _, wantEv := range want { 366 nmatch := 0 367 for ; len(have) > 0; nmatch++ { 368 if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { 369 break 370 } 371 have = have[1:] 372 } 373 if nmatch == 0 { 374 t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) 375 } 376 } 377 } 378 379 func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { 380 d, err := ioutil.TempDir("", "eth-keystore-test") 381 if err != nil { 382 t.Fatal(err) 383 } 384 newKs := NewPlaintextKeyStore 385 if encrypted { 386 newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } 387 } 388 return d, newKs(d) 389 }