decred.org/dcrwallet/v3@v3.1.0/wallet/udb/addressmanager_test.go (about) 1 // Copyright (c) 2014 The btcsuite developers 2 // Copyright (c) 2015-2019 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package udb 7 8 import ( 9 "bytes" 10 "context" 11 "fmt" 12 "os" 13 "path/filepath" 14 "reflect" 15 "testing" 16 17 "decred.org/dcrwallet/v3/errors" 18 "decred.org/dcrwallet/v3/wallet/walletdb" 19 "github.com/decred/dcrd/chaincfg/v3" 20 "github.com/decred/dcrd/dcrutil/v4" 21 "github.com/decred/dcrd/hdkeychain/v3" 22 "github.com/decred/dcrd/txscript/v4/stdaddr" 23 ) 24 25 // testContext is used to store context information about a running test which 26 // is passed into helper functions. 27 type testContext struct { 28 t *testing.T 29 db walletdb.DB 30 manager *Manager 31 account uint32 32 create bool 33 unlocked bool 34 watchingOnly bool 35 } 36 37 // expectedAddr is used to house the expected return values from a managed 38 // address. Not all fields for used for all managed address types. 39 type expectedAddr struct { 40 address string 41 addressHash []byte 42 internal bool 43 compressed bool 44 imported bool 45 pubKey []byte 46 privKey []byte 47 privKeyWIF string 48 script []byte 49 } 50 51 // testNamePrefix is a helper to return a prefix to show for test errors based 52 // on the state of the test context. 53 func testNamePrefix(tc *testContext) string { 54 prefix := "Open " 55 if tc.create { 56 prefix = "Create " 57 } 58 59 return prefix + fmt.Sprintf("account #%d", tc.account) 60 } 61 62 // testManagedPubKeyAddress ensures the data returned by exported functions 63 // provided by the passed managed public key address matches the corresponding 64 // fields in the provided expected address. 65 func testManagedPubKeyAddress(tc *testContext, prefix string, gotAddr ManagedPubKeyAddress, wantAddr *expectedAddr) bool { 66 // Ensure pubkey is the expected value for the managed address. 67 gpubBytes := gotAddr.PubKey() 68 69 if !bytes.Equal(gpubBytes, wantAddr.pubKey) { 70 tc.t.Errorf("%s PubKey: unexpected public key - got %x, want "+ 71 "%x", prefix, gpubBytes, wantAddr.pubKey) 72 return false 73 } 74 75 return true 76 } 77 78 // testManagedScriptAddress ensures the data returned by exported functions 79 // provided by the passed managed script address matches the corresponding 80 // fields in the provided expected address. 81 func testManagedScriptAddress(tc *testContext, prefix string, gotAddr ManagedScriptAddress, wantAddr *expectedAddr) bool { 82 if !bytes.Equal(gotAddr.AddrHash(), 83 dcrutil.Hash160(wantAddr.script)) { 84 tc.t.Errorf("%s Script: unexpected script - got %x, want "+ 85 "%x", prefix, gotAddr.AddrHash(), 86 dcrutil.Hash160(wantAddr.script)) 87 return false 88 } 89 90 return true 91 } 92 93 // testAddress ensures the data returned by all exported functions provided by 94 // the passed managed address matches the corresponding fields in the provided 95 // expected address. It also type asserts the managed address to determine its 96 // specific type and calls the corresponding testing functions accordingly. 97 func testAddress(tc *testContext, prefix string, gotAddr ManagedAddress, wantAddr *expectedAddr) bool { 98 if gotAddr.Account() != tc.account { 99 tc.t.Errorf("ManagedAddress.Account: unexpected account - got "+ 100 "%d, want %d", gotAddr.Account(), tc.account) 101 return false 102 } 103 104 if gotAddr.Address().String() != wantAddr.address { 105 tc.t.Errorf("%s EncodeAddress: unexpected address - got %s, "+ 106 "want %s", prefix, gotAddr.Address().String(), 107 wantAddr.address) 108 return false 109 } 110 111 if !bytes.Equal(gotAddr.AddrHash(), wantAddr.addressHash) { 112 tc.t.Errorf("%s AddrHash: unexpected address hash - got %x, "+ 113 "want %x", prefix, gotAddr.AddrHash(), wantAddr.addressHash) 114 return false 115 } 116 117 if gotAddr.Internal() != wantAddr.internal { 118 tc.t.Errorf("%s Internal: unexpected internal flag - got %v, "+ 119 "want %v", prefix, gotAddr.Internal(), wantAddr.internal) 120 return false 121 } 122 123 if gotAddr.Imported() != wantAddr.imported { 124 tc.t.Errorf("%s Imported: unexpected imported flag - got %v, "+ 125 "want %v", prefix, gotAddr.Imported(), wantAddr.imported) 126 return false 127 } 128 129 switch addr := gotAddr.(type) { 130 case ManagedPubKeyAddress: 131 if !testManagedPubKeyAddress(tc, prefix, addr, wantAddr) { 132 return false 133 } 134 135 case ManagedScriptAddress: 136 if !testManagedScriptAddress(tc, prefix, addr, wantAddr) { 137 return false 138 } 139 } 140 141 return true 142 } 143 144 // testLocking tests the basic locking semantics of the address manager work 145 // as expected. Other tests ensure addresses behave as expected under locked 146 // and unlocked conditions. 147 func testLocking(tc *testContext, rb walletdb.ReadBucket) { 148 if tc.unlocked { 149 tc.t.Fatal("testLocking called with an unlocked manager") 150 } 151 if !tc.manager.IsLocked() { 152 tc.t.Fatal("IsLocked: returned false on locked manager") 153 } 154 155 // Locking an already lock manager should return an error. The error 156 // should be ErrLocked or ErrWatchingOnly depending on the type of the 157 // address manager. 158 err := tc.manager.Lock() 159 wantErrCode := errors.Locked 160 if tc.watchingOnly { 161 wantErrCode = errors.WatchingOnly 162 } 163 if !errors.Is(err, wantErrCode) { 164 tc.t.Fatalf("Lock: unexpected error: %v", err) 165 } 166 167 // Ensure unlocking with the correct passphrase doesn't return any 168 // unexpected errors and the manager properly reports it is unlocked. 169 // Since watching-only address managers can't be unlocked, also ensure 170 // the correct error for that case. 171 err = tc.manager.Unlock(rb, privPassphrase) 172 if tc.watchingOnly { 173 if !errors.Is(err, errors.WatchingOnly) { 174 tc.t.Fatalf("Unlock: unexpected error: %v", err) 175 } 176 } else if err != nil { 177 tc.t.Fatalf("Unlock: unexpected error: %v", err) 178 } 179 if !tc.watchingOnly && tc.manager.IsLocked() { 180 tc.t.Fatal("IsLocked: returned true on unlocked manager") 181 } 182 183 // Unlocking the manager again is allowed. Since watching-only address 184 // managers can't be unlocked, also ensure the correct error for that 185 // case. 186 err = tc.manager.Unlock(rb, privPassphrase) 187 if tc.watchingOnly { 188 if !errors.Is(err, errors.WatchingOnly) { 189 tc.t.Fatalf("Unlock: unexpected error: %v", err) 190 } 191 } else if err != nil { 192 tc.t.Fatalf("Unlock: unexpected error: %v", err) 193 } 194 if !tc.watchingOnly && tc.manager.IsLocked() { 195 tc.t.Fatal("IsLocked: returned true on unlocked manager") 196 } 197 198 // Unlocking the manager with an invalid passphrase must result in an 199 // error and a locked manager. 200 err = tc.manager.Unlock(rb, []byte("invalidpassphrase")) 201 wantErrCode = errors.Passphrase 202 if tc.watchingOnly { 203 wantErrCode = errors.WatchingOnly 204 } 205 if !errors.Is(err, wantErrCode) { 206 tc.t.Fatalf("Unlock: unexpected error: %v", err) 207 } 208 if !tc.manager.IsLocked() { 209 tc.t.Fatal("IsLocked: manager is unlocked after failed unlock attempt") 210 } 211 } 212 213 // testImportPrivateKey tests that importing private keys works properly. It 214 // ensures they can be retrieved by Address after they have been imported and 215 // the addresses give the expected values when the manager is locked and 216 // unlocked. 217 // 218 // This function expects the manager is already locked when called and returns 219 // with the manager locked. 220 func testImportPrivateKey(tc *testContext, ns walletdb.ReadWriteBucket) { 221 if !tc.create { 222 return 223 } 224 225 // The manager cannot be unlocked to import a private key in watching-only 226 // mode. 227 if tc.watchingOnly { 228 return 229 } 230 231 tests := []struct { 232 name string 233 in string 234 expected expectedAddr 235 }{ 236 { 237 name: "wif for compressed pubkey address", 238 in: "PtWUqkS3apLoZUevFtG3Bwt6uyX8LQfYttycGkt2XCzgxquPATQgG", 239 expected: expectedAddr{ 240 address: "TsSYVKf24LcrxyWHBqj4oBcU542PcjH1iA2", 241 addressHash: hexToBytes("10b601a41d2320527c95eb4cdae2c75b45ae45e1"), 242 internal: false, 243 imported: true, 244 compressed: true, 245 pubKey: hexToBytes("03df8852b90ce8da7de6bcbacd26b78534ad9e46dc1b62a01dcf43f5837d7f9f5e"), 246 privKey: hexToBytes("ac4cb1a53c4f04a71fffbff26d4500c8a95443936deefd1b6ed89727a6858e08"), 247 // privKeyWIF is set to the in field during tests 248 }, 249 }, 250 } 251 252 if err := tc.manager.Unlock(ns, privPassphrase); err != nil { 253 tc.t.Fatalf("Unlock: unexpected error: %v", err) 254 } 255 tc.unlocked = true 256 257 // Only import the private keys when in the create phase of testing. 258 tc.account = ImportedAddrAccount 259 prefix := testNamePrefix(tc) + " testImportPrivateKey" 260 chainParams := tc.manager.ChainParams() 261 for i, test := range tests { 262 test.expected.privKeyWIF = test.in 263 wif, err := dcrutil.DecodeWIF(test.in, chainParams.PrivateKeyID) 264 if err != nil { 265 tc.t.Errorf("%s DecodeWIF #%d (%s) (%s): unexpected "+ 266 "error: %v", prefix, i, test.in, test.name, err) 267 continue 268 } 269 addr, err := tc.manager.ImportPrivateKey(ns, wif) 270 if err != nil { 271 tc.t.Fatalf("%s ImportPrivateKey #%d (%s): "+ 272 "unexpected error: %v", prefix, i, 273 test.name, err) 274 } 275 if !testAddress(tc, prefix+" ImportPrivateKey", addr, &test.expected) { 276 continue 277 } 278 } 279 280 // Setup a closure to test the results since the same tests need to be 281 // repeated with the manager unlocked and locked. 282 testResults := func() { 283 for i, test := range tests { 284 test.expected.privKeyWIF = test.in 285 286 // Use the Address API to retrieve each of the expected 287 // new addresses and ensure they're accurate. 288 utilAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1(0, 289 test.expected.addressHash, chainParams) 290 if err != nil { 291 tc.t.Fatalf("%s NewAddressPubKeyHash #%d (%s): "+ 292 "unexpected error: %v", prefix, i, test.name, err) 293 } 294 taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, 295 i, test.name) 296 ma, err := tc.manager.Address(ns, utilAddr) 297 if err != nil { 298 tc.t.Fatalf("%s: unexpected error: %v", taPrefix, err) 299 continue 300 } 301 if !testAddress(tc, taPrefix, ma, &test.expected) { 302 tc.t.Fatalf("testAddress for %v failed", ma.Address()) 303 } 304 } 305 } 306 307 // The address manager could either be locked or unlocked here depending 308 // on whether or not it's a watching-only manager. When it's unlocked, 309 // this will test both the public and private address data are accurate. 310 // When it's locked, it must be watching-only, so only the public 311 // address nformation is tested and the private functions are checked 312 // to ensure they return the expected ErrWatchingOnly error. 313 testResults() 314 315 // Lock the manager and retest all of the addresses to ensure the 316 // private information returns the expected error. 317 if err := tc.manager.Lock(); err != nil { 318 tc.t.Fatalf("Lock: unexpected error: %v", err) 319 } 320 tc.unlocked = false 321 322 testResults() 323 } 324 325 // testImportScript tests that importing scripts works properly. It ensures 326 // they can be retrieved by Address after they have been imported and the 327 // addresses give the expected values when the manager is locked and unlocked. 328 // 329 // This function expects the manager is already locked when called and returns 330 // with the manager locked. 331 func testImportScript(tc *testContext, wb walletdb.ReadWriteBucket) { 332 if !tc.create { 333 return 334 } 335 336 // The manager cannot be unlocked to import a script key in watching-only 337 // mode. 338 if tc.watchingOnly { 339 return 340 } 341 342 tests := []struct { 343 name string 344 in []byte 345 expected expectedAddr 346 }{ 347 { 348 name: "p2sh multisig", 349 in: hexToBytes("51210373c717acda38b5aa4c00c33932e059cdbc" + 350 "11deceb5f00490a9101704cc444c5151ae"), 351 expected: expectedAddr{ 352 address: "TcsXPUraiDWZoeQBEbw7T7LSgrvD7dar9DA", 353 addressHash: hexToBytes("db7e6d507e3e291a5ab2fac10107f4479c1f4f9c"), 354 internal: false, 355 imported: true, 356 compressed: false, 357 // script is set to the in field during tests. 358 }, 359 }, 360 } 361 362 rb := wb.(walletdb.ReadBucket) 363 364 if err := tc.manager.Unlock(rb, privPassphrase); err != nil { 365 tc.t.Fatalf("Unlock: unexpected error: %v", err) 366 } 367 tc.unlocked = true 368 369 // Only import the scripts when in the create phase of testing. 370 tc.account = ImportedAddrAccount 371 prefix := testNamePrefix(tc) 372 for i, test := range tests { 373 test.expected.script = test.in 374 prefix := fmt.Sprintf("%s ImportScript #%d (%s)", prefix, 375 i, test.name) 376 377 addr, err := tc.manager.ImportScript(wb, test.in) 378 if err != nil { 379 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 380 } 381 if !testAddress(tc, prefix, addr, &test.expected) { 382 tc.t.Fatalf("%s: testAddress failed for %v", prefix, 383 addr.Address()) 384 } 385 } 386 387 // Setup a closure to test the results since the same tests need to be 388 // repeated with the manager unlocked and locked. 389 chainParams := tc.manager.ChainParams() 390 testResults := func() { 391 for i, test := range tests { 392 test.expected.script = test.in 393 394 // Use the Address API to retrieve each of the expected 395 // new addresses and ensure they're accurate. 396 utilAddr, err := stdaddr.NewAddressScriptHashV0(test.in, 397 chainParams) 398 if err != nil { 399 tc.t.Fatalf("%s NewAddressScriptHash #%d (%s): "+ 400 "unexpected error: %v", prefix, i, test.name, err) 401 } 402 taPrefix := fmt.Sprintf("%s Address #%d (%s)", prefix, 403 i, test.name) 404 ma, err := tc.manager.Address(rb, utilAddr) 405 if err != nil { 406 tc.t.Fatalf("%s: unexpected error: %v", taPrefix, err) 407 } 408 if !testAddress(tc, taPrefix, ma, &test.expected) { 409 tc.t.Fatalf("%s: testAddress failed for %v", prefix, 410 ma.Address()) 411 } 412 } 413 } 414 415 // The address manager could either be locked or unlocked here depending 416 // on whether or not it's a watching-only manager. When it's unlocked, 417 // this will test both the public and private address data are accurate. 418 // When it's locked, it must be watching-only, so only the public 419 // address information is tested and the private functions are checked 420 // to ensure they return the expected ErrWatchingOnly error. 421 testResults() 422 423 // Lock the manager and retest all of the addresses to ensure the 424 // private information returns the expected error. 425 if err := tc.manager.Lock(); err != nil { 426 tc.t.Fatalf("Lock: unexpected error: %v", err) 427 } 428 tc.unlocked = false 429 430 testResults() 431 } 432 433 func TestManagerImports(t *testing.T) { 434 ctx := context.Background() 435 db, mgr, _, _, teardown, err := cloneDB(ctx, "imports.kv") 436 defer teardown() 437 if err != nil { 438 t.Fatal(err) 439 } 440 defer mgr.Close() 441 442 tc := &testContext{ 443 t: t, 444 db: db, 445 manager: mgr, 446 account: 0, 447 create: true, 448 watchingOnly: false, 449 } 450 451 testImports := func(tc *testContext) { 452 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 453 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 454 testImportPrivateKey(tc, ns) 455 testImportScript(tc, ns) 456 return nil 457 }) 458 if err != nil { 459 t.Fatalf("unexpected error: %v", err) 460 } 461 } 462 463 testImports(tc) 464 465 tc.create = false 466 tc.watchingOnly = true 467 468 testImports(tc) 469 } 470 471 // TestImportVotingAccount tests that importing voting accounts works properly. 472 // 473 // This function expects the manager is already locked when called and returns 474 // with the manager locked. 475 func TestImportVotingAccount(t *testing.T) { 476 ctx := context.Background() 477 db, mgr, _, _, teardown, err := cloneDB(ctx, "import_voting_account.kv") 478 defer teardown() 479 if err != nil { 480 t.Fatal(err) 481 } 482 defer mgr.Close() 483 privTestNet, err := hdkeychain.NewKeyFromString("tprvZUo1ZuEfLLFWg9kt"+ 484 "EaXHf3HKq2EdfwY5pXFZ2rbg6HFzeaoJDp1qFZnuuuN6iUAG9EyNF4sH4RmJ"+ 485 "b395XWYpdqQoXRoKkV88HZwgq95KfiK", chaincfg.TestNet3Params()) 486 if err != nil { 487 t.Fatalf("unable to parse xpriv: %v", err) 488 } 489 account1 := "account 1" 490 491 tests := []struct { 492 name, pass, acctName string 493 want uint32 494 wantErr *errors.Error 495 watchingOnly, unlock bool 496 }{{ 497 name: "ok watching only", 498 acctName: account1, 499 pass: "abc", 500 want: ImportedAddrAccount + 1, 501 watchingOnly: true, 502 }, { 503 name: "ok locked", 504 acctName: "account 2", 505 pass: "abc", 506 want: ImportedAddrAccount + 2, 507 }, { 508 name: "ok unlocked", 509 acctName: "account 3", 510 pass: "abc", 511 unlock: true, 512 want: ImportedAddrAccount + 3, 513 }, { 514 name: "name taken", 515 acctName: account1, 516 pass: "abc", 517 wantErr: &errors.Error{Kind: errors.Exist}, 518 }, { 519 name: "no password", 520 acctName: "account 4", 521 wantErr: &errors.Error{Kind: errors.Passphrase}, 522 }, { 523 name: "no name", 524 pass: "abc", 525 wantErr: &errors.Error{Kind: errors.Invalid}, 526 }} 527 528 for i, test := range tests { 529 tc := &testContext{ 530 t: t, 531 db: db, 532 manager: mgr, 533 create: true, 534 watchingOnly: test.watchingOnly, 535 } 536 prefix := testNamePrefix(tc) 537 prefix = fmt.Sprintf("%s ImportVotingAccount #%d (%s)", prefix, 538 i, test.name) 539 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 540 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 541 if test.unlock { 542 if err := tc.manager.Unlock(ns, privPassphrase); err != nil { 543 tc.t.Fatalf("%s: Unlock: unexpected error: %v", prefix, err) 544 } 545 tc.unlocked = true 546 defer func() { 547 if err := tc.manager.Lock(); err != nil { 548 tc.t.Fatalf("%s: Lock: unexpected error: %v", 549 prefix, err) 550 } 551 tc.unlocked = false 552 }() 553 } 554 pass := []byte(test.pass) 555 got, err := tc.manager.ImportVotingAccount(tx, privTestNet, 556 pass, test.acctName) 557 if test.wantErr != nil { 558 if err == nil { 559 tc.t.Fatalf("%s: wanted error %v but got none", 560 prefix, test.wantErr) 561 } 562 kind := err.(*errors.Error).Kind 563 if !test.wantErr.Is(kind) { 564 tc.t.Fatalf("%s: wanted error %v but got %v", 565 prefix, test.wantErr, kind) 566 } 567 return nil 568 } 569 if err != nil { 570 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 571 } 572 if got != test.want { 573 tc.t.Fatalf("%s: wanted account number %d but got %d", prefix, 574 test.want, got) 575 } 576 577 if err := tc.manager.UnlockAccount(tx, got, pass); err != nil { 578 tc.t.Fatalf("%s: Unlock: unexpected error: %v", prefix, err) 579 } 580 defer tc.manager.mtx.Unlock() 581 tc.manager.mtx.Lock() 582 acctInfo, err := tc.manager.loadAccountInfo(ns, got) 583 if err != nil { 584 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 585 } 586 // Enusure the account type is importedVoting. 587 if acctInfo.acctType != importedVoting { 588 tc.t.Fatalf("%s: wanted account type %v but got %v", prefix, 589 importedVoting, acctInfo.acctType) 590 } 591 // Enusure xpriv is the same. 592 if acctInfo.acctKeyPriv.String() != privTestNet.String() { 593 tc.t.Fatalf("%s: wanted account xpriv %v but got %v", prefix, 594 privTestNet, acctInfo.acctKeyPriv) 595 } 596 return nil 597 }) 598 if err != nil { 599 t.Fatalf("unexpected error: %v", err) 600 } 601 } 602 } 603 604 // TestImportAccount tests that importing accounts works properly. 605 // 606 // This function expects the manager is already locked when called and returns 607 // with the manager locked. 608 func TestImportAccount(t *testing.T) { 609 ctx := context.Background() 610 db, mgr, _, _, teardown, err := cloneDB(ctx, "import_account.kv") 611 defer teardown() 612 if err != nil { 613 t.Fatal(err) 614 } 615 defer mgr.Close() 616 privTestNet, err := hdkeychain.NewKeyFromString("tprvZUo1ZuEfLLFWg9kt"+ 617 "EaXHf3HKq2EdfwY5pXFZ2rbg6HFzeaoJDp1qFZnuuuN6iUAG9EyNF4sH4RmJ"+ 618 "b395XWYpdqQoXRoKkV88HZwgq95KfiK", chaincfg.TestNet3Params()) 619 if err != nil { 620 t.Fatalf("unable to parse xpriv: %v", err) 621 } 622 account1 := "account 1" 623 624 tests := []struct { 625 name, acctName string 626 want uint32 627 wantErr *errors.Error 628 watchingOnly, discoverUsage bool 629 }{{ 630 name: "ok watching only", 631 acctName: account1, 632 want: ImportedAddrAccount + 1, 633 watchingOnly: true, 634 }, { 635 name: "ok", 636 acctName: "account 2", 637 want: ImportedAddrAccount + 2, 638 }, { 639 name: "name taken", 640 acctName: account1, 641 wantErr: &errors.Error{Kind: errors.Exist}, 642 }, { 643 name: "address exists", 644 acctName: "account 3", 645 discoverUsage: true, 646 wantErr: &errors.Error{Kind: errors.Exist}, 647 }, { 648 name: "no name", 649 wantErr: &errors.Error{Kind: errors.Invalid}, 650 }} 651 652 for i, test := range tests { 653 tc := &testContext{ 654 t: t, 655 db: db, 656 manager: mgr, 657 create: true, 658 watchingOnly: test.watchingOnly, 659 } 660 prefix := testNamePrefix(tc) 661 prefix = fmt.Sprintf("%s importAccount #%d (%s)", prefix, 662 i, test.name) 663 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 664 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 665 if err := tc.manager.Unlock(ns, privPassphrase); err != nil { 666 tc.t.Fatalf("%s: Unlock: unexpected error: %v", prefix, err) 667 } 668 tc.unlocked = true 669 defer func() { 670 if err := tc.manager.Lock(); err != nil { 671 tc.t.Fatalf("%s: Lock: unexpected error: %v", 672 prefix, err) 673 } 674 tc.unlocked = false 675 }() 676 if test.discoverUsage { 677 err = mgr.SyncAccountToAddrIndex(ns, ImportedAddrAccount+1, 0, ExternalBranch) 678 if err != nil { 679 return err 680 } 681 } 682 got, err := tc.manager.importAccount(tx, actBIP0044, 683 privTestNet, test.acctName) 684 if test.wantErr != nil { 685 if err == nil { 686 tc.t.Fatalf("%s: wanted error %v but got none", 687 prefix, test.wantErr) 688 } 689 kind := err.(*errors.Error).Kind 690 if !test.wantErr.Is(kind) { 691 tc.t.Fatalf("%s: wanted error %v but got %v", 692 prefix, test.wantErr, kind) 693 } 694 return nil 695 } 696 if err != nil { 697 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 698 } 699 if got != test.want { 700 tc.t.Fatalf("%s: wanted account number %d but got %d", prefix, 701 test.want, got) 702 } 703 defer tc.manager.mtx.Unlock() 704 tc.manager.mtx.Lock() 705 acctInfo, err := tc.manager.loadAccountInfo(ns, got) 706 if err != nil { 707 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 708 } 709 // Enusure xpriv is the same. 710 if acctInfo.acctKeyPriv.String() != privTestNet.String() { 711 tc.t.Fatalf("%s: wanted account xpriv %v but got %v", prefix, 712 privTestNet, acctInfo.acctKeyPriv) 713 } 714 return nil 715 }) 716 if err != nil { 717 t.Fatalf("unexpected error: %v", err) 718 } 719 } 720 } 721 722 // testChangePassphrase ensures changes both the public and private passphrases 723 // works as intended. 724 func testChangePassphrase(tc *testContext, wb walletdb.ReadWriteBucket) { 725 // Force an error when changing the passphrase due to failure to 726 // generate a new secret key by replacing the generation function one 727 // that intentionally errors. 728 testName := "ChangePassphrase (public) with invalid new secret key" 729 730 var err error 731 TstRunWithReplacedNewSecretKey(func() { 732 err = tc.manager.ChangePassphrase(wb, pubPassphrase, pubPassphrase2, false) 733 }) 734 if !errors.Is(err, errors.Crypto) { 735 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 736 } 737 738 rb := wb.(walletdb.ReadBucket) 739 740 // Attempt to change public passphrase with invalid old passphrase. 741 testName = "ChangePassphrase (public) with invalid old passphrase" 742 err = tc.manager.ChangePassphrase(wb, []byte("bogus"), pubPassphrase2, false) 743 if !errors.Is(err, errors.Passphrase) { 744 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 745 } 746 747 // Change the public passphrase. 748 testName = "ChangePassphrase (public)" 749 err = tc.manager.ChangePassphrase(wb, pubPassphrase, pubPassphrase2, false) 750 if err != nil { 751 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 752 } 753 754 // Ensure the public passphrase was successfully changed. 755 if !tc.manager.TstCheckPublicPassphrase(pubPassphrase2) { 756 tc.t.Fatalf("%s: passphrase does not match", testName) 757 } 758 759 // Change the private passphrase back to what it was. 760 err = tc.manager.ChangePassphrase(wb, pubPassphrase2, pubPassphrase, false) 761 if err != nil { 762 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 763 } 764 765 // Attempt to change private passphrase with invalid old passphrase. 766 // The error should be ErrWrongPassphrase or ErrWatchingOnly depending 767 // on the type of the address manager. 768 testName = "ChangePassphrase (private) with invalid old passphrase" 769 err = tc.manager.ChangePassphrase(wb, []byte("bogus"), privPassphrase2, true) 770 wantErrCode := errors.Passphrase 771 if tc.watchingOnly { 772 wantErrCode = errors.WatchingOnly 773 } 774 if !errors.Is(err, wantErrCode) { 775 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 776 } 777 778 // Everything after this point involves testing that the private 779 // passphrase for the address manager can be changed successfully. 780 // This is not possible for watching-only mode, so just exit now in that 781 // case. 782 if tc.watchingOnly { 783 return 784 } 785 786 // Change the private passphrase. 787 testName = "ChangePassphrase (private)" 788 err = tc.manager.ChangePassphrase(wb, privPassphrase, privPassphrase2, true) 789 if err != nil { 790 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 791 } 792 793 // Unlock the manager with the new passphrase to ensure it changed as 794 // expected. 795 if err := tc.manager.Unlock(rb, privPassphrase2); err != nil { 796 tc.t.Fatalf("%s: failed to unlock with new private "+ 797 "passphrase: %v", testName, err) 798 } 799 tc.unlocked = true 800 801 // Change the private passphrase back to what it was while the manager 802 // is unlocked to ensure that path works properly as well. 803 err = tc.manager.ChangePassphrase(wb, privPassphrase2, privPassphrase, true) 804 if err != nil { 805 tc.t.Fatalf("%s: unexpected error: %v", testName, err) 806 } 807 if tc.manager.IsLocked() { 808 tc.t.Fatalf("%s: manager is locked", testName) 809 } 810 811 // Relock the manager for future tests. 812 if err := tc.manager.Lock(); err != nil { 813 tc.t.Fatalf("Lock: unexpected error: %v", err) 814 } 815 tc.unlocked = false 816 } 817 818 // testNewAccount tests the new account creation func of the address manager works 819 // as expected. 820 func testNewAccount(tc *testContext, wb walletdb.ReadWriteBucket) { 821 if !tc.create { 822 return 823 } 824 825 if tc.watchingOnly { 826 // Creating new accounts in watching-only mode should return ErrWatchingOnly 827 _, err := tc.manager.NewAccount(wb, "test") 828 if !errors.Is(err, errors.WatchingOnly) { 829 tc.t.Fatalf("NewAccount: expected ErrWatchingOnly, got %v", err) 830 } 831 } 832 833 // Creating new accounts when wallet is locked should return ErrLocked 834 _, err := tc.manager.NewAccount(wb, "test") 835 if !errors.Is(err, errors.Locked) { 836 tc.t.Fatalf("NewAccount: expected ErrLocked, got %v", err) 837 } 838 839 rb := wb.(walletdb.ReadBucket) 840 841 // Unlock the wallet to decrypt cointype keys required to derive 842 // account keys 843 if err := tc.manager.Unlock(rb, privPassphrase); err != nil { 844 tc.t.Fatalf("Unlock: unexpected error: %v", err) 845 } 846 tc.unlocked = true 847 848 testName := "acct-create" 849 expectedAccount := tc.account + 1 850 account, err := tc.manager.NewAccount(wb, testName) 851 if err != nil { 852 tc.t.Fatalf("NewAccount: unexpected error: %v", err) 853 } 854 if account != expectedAccount { 855 tc.t.Fatalf("NewAccount account mismatch -- got %d, want %d", 856 account, expectedAccount) 857 } 858 859 // Test duplicate account name error 860 _, err = tc.manager.NewAccount(wb, testName) 861 if !errors.Is(err, errors.Exist) { 862 tc.t.Fatalf("NewAccount: expected ErrExist, got %v", err) 863 } 864 // Test account name validation 865 testName = "" // Empty account names are not allowed 866 _, err = tc.manager.NewAccount(wb, testName) 867 if !errors.Is(err, errors.Invalid) { 868 tc.t.Fatalf("NewAccount: expected ErrInvalid, got %v", err) 869 } 870 testName = "imported" // A reserved account name 871 _, err = tc.manager.NewAccount(wb, testName) 872 if !errors.Is(err, errors.Invalid) { 873 tc.t.Fatalf("NewAccount: expected ErrInvalid, got %v", err) 874 } 875 } 876 877 // testLookupAccount tests the basic account lookup func of the address manager 878 // works as expected. 879 func testLookupAccount(tc *testContext, rb walletdb.ReadBucket) { 880 // Lookup accounts created earlier in testNewAccount 881 expectedAccounts := map[string]uint32{ 882 TstDefaultAccountName: DefaultAccountNum, 883 ImportedAddrAccountName: ImportedAddrAccount, 884 } 885 for acctName, expectedAccount := range expectedAccounts { 886 account, err := tc.manager.LookupAccount(rb, acctName) 887 if err != nil { 888 tc.t.Fatalf("LookupAccount: unexpected error: %v", err) 889 } 890 if account != expectedAccount { 891 tc.t.Fatalf("LookupAccount account mismatch -- got %d, want %d", 892 account, expectedAccount) 893 } 894 } 895 // Test account not found error 896 testName := "non existent account" 897 _, err := tc.manager.LookupAccount(rb, testName) 898 if !errors.Is(err, errors.NotExist) { 899 tc.t.Fatalf("LookupAccount: unexpected error: %v", err) 900 } 901 902 // Test last account 903 lastAccount, err := tc.manager.LastAccount(rb) 904 if err != nil { 905 tc.t.Fatalf("LastAccount failed: %v", err) 906 } 907 908 expectedLastAccount := uint32(1) 909 if lastAccount != expectedLastAccount { 910 tc.t.Fatalf("LookupAccount account mismatch -- got %d, want %d", 911 lastAccount, expectedLastAccount) 912 } 913 } 914 915 // testRenameAccount tests the rename account func of the address manager works 916 // as expected. 917 func testRenameAccount(tc *testContext, wb walletdb.ReadWriteBucket) { 918 rb := wb.(walletdb.ReadBucket) 919 acctName, err := tc.manager.AccountName(rb, tc.account) 920 if err != nil { 921 tc.t.Fatalf("AccountName: unexpected error: %v", err) 922 } 923 testName := acctName + "-renamed" 924 err = tc.manager.RenameAccount(wb, tc.account, testName) 925 if err != nil { 926 tc.t.Fatalf("RenameAccount: unexpected error: %v", err) 927 } 928 newName, err := tc.manager.AccountName(rb, tc.account) 929 if err != nil { 930 tc.t.Fatalf("AccountName: unexpected error: %v", err) 931 } 932 if newName != testName { 933 tc.t.Fatalf("RenameAccount account name mismatch -- got %s, want %s", 934 newName, testName) 935 } 936 // Test duplicate account name error 937 err = tc.manager.RenameAccount(wb, tc.account, testName) 938 if !errors.Is(err, errors.Exist) { 939 tc.t.Fatalf("RenameAccount: unexpected error: %v", err) 940 } 941 // Test old account name is no longer valid 942 _, err = tc.manager.LookupAccount(wb, acctName) 943 if err == nil { 944 tc.t.Fatalf("LookupAccount: unexpected error: %v", err) 945 } 946 if !errors.Is(err, errors.NotExist) { 947 tc.t.Fatalf("LookupAccount: unexpected error: %v", err) 948 } 949 } 950 951 // testForEachAccount tests the retrieve all accounts func of the address 952 // manager works as expected. 953 func testForEachAccount(tc *testContext, rb walletdb.ReadBucket) { 954 prefix := testNamePrefix(tc) + " testForEachAccount" 955 expectedAccounts := []uint32{0, 1, 2147483647} 956 957 var accounts []uint32 958 err := tc.manager.ForEachAccount(rb, func(account uint32) error { 959 accounts = append(accounts, account) 960 return nil 961 }) 962 if err != nil { 963 tc.t.Fatalf("%s: unexpected error: %v", prefix, err) 964 } 965 if len(accounts) != len(expectedAccounts) { 966 tc.t.Fatalf("%s: unexpected number of accounts - got "+ 967 "%d, want %d", prefix, len(accounts), len(expectedAccounts)) 968 } 969 for i, account := range accounts { 970 if expectedAccounts[i] != account { 971 tc.t.Fatalf("%s #%d: account mismatch -- got %d, want %d,"+ 972 " accounts: %v", prefix, i, account, expectedAccounts[i], accounts) 973 } 974 } 975 } 976 977 // testEncryptDecryptErrors ensures that errors which occur while encrypting and 978 // decrypting data return the expected errors. 979 func testEncryptDecryptErrors(ctx context.Context, tc *testContext) { 980 invalidKeyType := CryptoKeyType(0xff) 981 if _, err := tc.manager.Encrypt(invalidKeyType, []byte{}); err == nil { 982 tc.t.Fatal("Encrypt accepted an invalid key type!") 983 } 984 985 if _, err := tc.manager.Decrypt(invalidKeyType, []byte{}); err == nil { 986 tc.t.Fatal("Encrypt accepted an invalid key type!") 987 } 988 989 if !tc.manager.IsLocked() { 990 tc.t.Fatal("Manager should be locked at this point.") 991 } 992 993 var err error 994 // Now the mgr is locked and encrypting/decrypting with private 995 // keys should fail. 996 _, err = tc.manager.Encrypt(CKTPrivate, []byte{}) 997 if !errors.Is(err, errors.Locked) { 998 tc.t.Fatal("encryption with private key should fail when manager" + 999 " is locked") 1000 } 1001 1002 _, err = tc.manager.Decrypt(CKTPrivate, []byte{}) 1003 if !errors.Is(err, errors.Locked) { 1004 tc.t.Fatal("decryption with private key should fail when manager" + 1005 " is locked") 1006 1007 } 1008 1009 err = walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 1010 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 1011 // Unlock the manager for these tests 1012 return tc.manager.Unlock(ns, privPassphrase) 1013 }) 1014 if err != nil { 1015 tc.t.Fatalf("Attempted to unlock the manager, but failed: %v", err) 1016 } 1017 1018 // Make sure to cover the ErrCrypto error path in Encrypt. 1019 TstRunWithFailingCryptoKeyPriv(tc.manager, func() { 1020 _, err = tc.manager.Encrypt(CKTPrivate, []byte{}) 1021 }) 1022 if !errors.Is(err, errors.Crypto) { 1023 tc.t.Fatal("failed encryption") 1024 } 1025 1026 // Lock the manager. 1027 if err := tc.manager.Lock(); err != nil { 1028 tc.t.Fatal("Attempted to lock the manager, but failed:", err) 1029 } 1030 } 1031 1032 // testEncryptDecrypt ensures that encrypting and decrypting data with the 1033 // the various crypto key types works as expected. 1034 func testEncryptDecrypt(ctx context.Context, tc *testContext) { 1035 plainText := []byte("this is a plaintext") 1036 1037 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 1038 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 1039 // Make sure address manager is unlocked 1040 return tc.manager.Unlock(ns, privPassphrase) 1041 }) 1042 if err != nil { 1043 tc.t.Fatal("Attempted to unlock the manager, but failed: ", err) 1044 } 1045 1046 keyTypes := []CryptoKeyType{ 1047 CKTPublic, 1048 CKTPrivate, 1049 } 1050 1051 for _, keyType := range keyTypes { 1052 cipherText, err := tc.manager.Encrypt(keyType, plainText) 1053 if err != nil { 1054 tc.t.Fatalf("Failed to encrypt plaintext: %v", err) 1055 } 1056 1057 decryptedCipherText, err := tc.manager.Decrypt(keyType, cipherText) 1058 if err != nil { 1059 tc.t.Fatalf("Failed to decrypt plaintext: %v", err) 1060 } 1061 1062 if !reflect.DeepEqual(decryptedCipherText, plainText) { 1063 tc.t.Fatal("Got:", decryptedCipherText, ", want:", plainText) 1064 } 1065 } 1066 1067 // Lock the manager. 1068 if err := tc.manager.Lock(); err != nil { 1069 tc.t.Fatal("Attempted to lock the manager, but failed:", err) 1070 } 1071 } 1072 1073 func TestManagerEncryptDecrypt(t *testing.T) { 1074 ctx := context.Background() 1075 1076 db, mgr, _, _, teardown, err := cloneDB(ctx, "encrypt_decrypt.kv") 1077 defer teardown() 1078 if err != nil { 1079 t.Fatal(err) 1080 } 1081 defer mgr.Close() 1082 1083 tc := &testContext{ 1084 t: t, 1085 db: db, 1086 manager: mgr, 1087 account: 0, 1088 create: false, 1089 watchingOnly: false, 1090 } 1091 1092 testEncryptDecryptErrors(ctx, tc) 1093 testEncryptDecrypt(ctx, tc) 1094 } 1095 1096 func TestChangePassphrase(t *testing.T) { 1097 ctx := context.Background() 1098 db, mgr, _, _, teardown, err := cloneDB(ctx, "change_passphrase.kv") 1099 defer teardown() 1100 if err != nil { 1101 t.Fatal(err) 1102 } 1103 defer mgr.Close() 1104 1105 tc := &testContext{ 1106 t: t, 1107 db: db, 1108 manager: mgr, 1109 account: 0, 1110 create: false, 1111 watchingOnly: false, 1112 } 1113 1114 err = walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 1115 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 1116 testChangePassphrase(tc, ns) 1117 return nil 1118 }) 1119 if err != nil { 1120 tc.t.Errorf("unexpected error: %v", err) 1121 } 1122 } 1123 1124 // testManagerAPI tests the functions provided by the Manager API. 1125 func testManagerAPI(ctx context.Context, tc *testContext) { 1126 err := walletdb.Update(ctx, tc.db, func(tx walletdb.ReadWriteTx) error { 1127 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 1128 1129 testLocking(tc, ns) 1130 1131 // Reset default account 1132 tc.account = 0 1133 testNewAccount(tc, ns) 1134 testLookupAccount(tc, ns) 1135 testForEachAccount(tc, ns) 1136 1137 // Rename account 1 "acct-create" 1138 tc.account = 1 1139 testRenameAccount(tc, ns) 1140 1141 return nil 1142 }) 1143 if err != nil { 1144 tc.t.Errorf("unexpected error: %v", err) 1145 } 1146 } 1147 1148 // TestManagerWatchingOnly tests various facets of a watching-only address 1149 // manager such as running the full set of API tests against a newly converted 1150 // copy as well as when it is opened from an existing namespace. 1151 func TestManagerWatchingOnly(t *testing.T) { 1152 ctx := context.Background() 1153 db, mgr, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") 1154 defer teardown() 1155 if err != nil { 1156 t.Fatal(err) 1157 } 1158 1159 // Run all of the manager API tests in create mode 1160 testManagerAPI(ctx, &testContext{ 1161 t: t, 1162 db: db, 1163 manager: mgr, 1164 account: 0, 1165 create: true, 1166 watchingOnly: false, 1167 }) 1168 mgr.Close() 1169 1170 mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) 1171 if err != nil { 1172 t.Fatalf("unexpected error: %v", err) 1173 } 1174 defer mgr.Close() 1175 1176 err = walletdb.Update(ctx, db, func(tx walletdb.ReadWriteTx) error { 1177 ns := tx.ReadWriteBucket(waddrmgrBucketKey) 1178 if err = mgr.ConvertToWatchingOnly(ns); err != nil { 1179 t.Fatalf("failed to convert manager to watching-only: %v", err) 1180 } 1181 return nil 1182 }) 1183 if err != nil { 1184 t.Fatal(err) 1185 } 1186 1187 // Run all of the manager API tests against the converted manager. 1188 testManagerAPI(ctx, &testContext{ 1189 t: t, 1190 db: db, 1191 manager: mgr, 1192 account: 0, 1193 create: false, 1194 watchingOnly: true, 1195 }) 1196 1197 // Open the watching-only manager and run all the tests again. 1198 mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) 1199 if err != nil { 1200 t.Fatalf("unexpected error: %v", err) 1201 } 1202 defer mgr.Close() 1203 1204 testManagerAPI(ctx, &testContext{ 1205 t: t, 1206 db: db, 1207 manager: mgr, 1208 account: 0, 1209 create: false, 1210 watchingOnly: true, 1211 }) 1212 } 1213 1214 // TestManager performs a full suite of tests against the address manager API in 1215 // create mode. It makes use of a test context because the address manager is 1216 // persistent and much of the testing involves having specific state. 1217 func TestManager(t *testing.T) { 1218 if testing.Short() { 1219 t.Skip("short: skipping TestManager") 1220 } 1221 1222 ctx := context.Background() 1223 db, mgr, _, _, teardown, err := cloneDB(ctx, "mgr_watching_only.kv") 1224 defer teardown() 1225 if err != nil { 1226 t.Fatal(err) 1227 } 1228 1229 testManagerAPI(ctx, &testContext{ 1230 t: t, 1231 db: db, 1232 manager: mgr, 1233 account: 0, 1234 create: true, 1235 watchingOnly: false, 1236 }) 1237 mgr.Close() 1238 1239 // Open the manager and run all the tests again in open mode which 1240 // avoids reinserting new addresses like the create mode tests do. 1241 mgr, _, _, err = Open(ctx, db, chaincfg.TestNet3Params(), pubPassphrase) 1242 if err != nil { 1243 t.Fatalf("Open: unexpected error: %v", err) 1244 } 1245 defer mgr.Close() 1246 1247 tc := &testContext{ 1248 t: t, 1249 db: db, 1250 manager: mgr, 1251 account: 0, 1252 create: false, 1253 watchingOnly: false, 1254 } 1255 testManagerAPI(ctx, tc) 1256 } 1257 1258 func TestMain(m *testing.M) { 1259 testDir, err := os.MkdirTemp("", "udb-") 1260 if err != nil { 1261 fmt.Printf("Unable to create temp directory: %v", err) 1262 os.Exit(1) 1263 } 1264 1265 emptyDbPath = filepath.Join(testDir, "empty.kv") 1266 teardown := func() { 1267 os.RemoveAll(testDir) 1268 } 1269 1270 ctx := context.Background() 1271 err = createEmptyDB(ctx) 1272 if err != nil { 1273 fmt.Printf("Unable to create empty test db: %v\n", err) 1274 teardown() 1275 os.Exit(1) 1276 } 1277 1278 exitCode := m.Run() 1279 teardown() 1280 os.Exit(exitCode) 1281 }