diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 17:14:12 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 17:14:12 +0000 |
| commit | b4f0bc693ef7175702f82bcb2110b627922e8cbd (patch) | |
| tree | 73435feb1a076dfba94d8143d7d7b7784908c82f /src/walletcmd.cpp | |
| parent | a1d867ecbcfc9e218408d0a11863a450243a01f9 (diff) | |
| parent | 6aa3dade77632f13189d2a3ef29897f29a5f9572 (diff) | |
Merge upstream v1.2.20 into the fork
Merges upstream tag v1.2.20 (6aa3dade) into our 1.2.19+sec tree. The merge
base is bc3de30's descendant 9e39a50 -- upstream's own v1.2.19 -- so this is
an ordinary three-way merge, not a replay. Five conflict blocks in four
files, all resolved below.
No consensus change. CheckBlock, ConnectBlock, DisconnectBlock, ConnectInputs,
AcceptBlock, AcceptTransaction, GetNextWorkRequired and GetBlockValue hash
identical to v1.2.19; script.cpp and script.h are byte-identical on both
sides. main.cpp is +3 lines and main.h +2, all wallet-backend plumbing.
rpc.cpp is untouched, so the payout ledger arrives unchanged.
Makefile, VERSION. OURS, renumbered. 1.2.20+sec, for the same reason 1.2.19
carried +sec: this tree is not the upstream release of that number and the
string names every release artifact.
Makefile, AppImage recipe. OURS for the AppDir path, THEIRS for the bundled
library. Upstream's hunk reverts the icon paths to a hardcoded
/tmp/bitflash.AppDir, which is the local-user pre-placement hazard the fork
moved out of /tmp in the first place; taking their side wholesale would have
reintroduced it silently, inside a conflict about something else. The AppDir
stays $(APPDIR), and libsqlite3.so.0 joins the bundled list, which the new
backend genuinely needs.
src/Makefile, src/makefile.mingw, HEADERS. BOTH. Ours drops market.h,
theirs adds wallet_sqlite.h; the resolution does both. NODE_OBJS auto-merged
correctly in both files -- obj/wallet_sqlite.o present, obj/market.o still
gone -- and was verified rather than assumed, because that line has trapped
this fork before.
src/selftest.cpp, known-tests list. BOTH, union. Upstream adds eight
wallet-storage tests, we keep payout-ledger. The dispatch chain itself
auto-merged and still routes payout-ledger to RunPayoutLedgerSelfTest.
Hardening verified intact after merge: the TLS 1.2 floor in nostr.cpp, the
/rvrelay pin, opt-in managed Tor, strict sig3-on-onion descriptor
verification, and the marketplace removal. Upstream shipped no build logs or
__pycache__ this time; the three added files are docs/storage.md and
src/wallet_sqlite.{cpp,h}.
The SQLite backend this release introduces is NOT enabled here. An existing
wallet.dat resolves to Berkeley DB on every branch of the new selection
logic, and the conversion wizard is GUI-only and not compiled into the
headless node. The review findings that gate ever turning it on -- the
non-atomic backend marker, the connection-wide WriteHDMaster transaction,
and the encrypt-swap crash window -- are recorded on the tracker, not fixed
here.
Diffstat (limited to 'src/walletcmd.cpp')
| -rw-r--r-- | src/walletcmd.cpp | 975 |
1 files changed, 970 insertions, 5 deletions
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index e8ac4bf..f59fc0c 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "headers_core.h" | 12 | #include "headers_core.h" |
| 13 | #include "bip32.h" | 13 | #include "bip32.h" |
| 14 | #include "walletcmd.h" | 14 | #include "walletcmd.h" |
| 15 | #include "wallet_sqlite.h" | ||
| 15 | 16 | ||
| 16 | #undef printf | 17 | #undef printf |
| 17 | 18 | ||
| @@ -400,7 +401,12 @@ int CmdRecoveryAudit() | |||
| 400 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; | 401 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; |
| 401 | 402 | ||
| 402 | printf("Wallet recovery audit\n"); | 403 | printf("Wallet recovery audit\n"); |
| 403 | printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); | 404 | if (audit.fHaveSeed) |
| 405 | printf(" recovery phrase: present\n"); | ||
| 406 | else if (audit.fSeedEncryptedLocked) | ||
| 407 | printf(" recovery phrase: encrypted, unlock wallet to audit\n"); | ||
| 408 | else | ||
| 409 | printf(" recovery phrase: not installed\n"); | ||
| 404 | if (audit.fHaveSeed) | 410 | if (audit.fHaveSeed) |
| 405 | { | 411 | { |
| 406 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); | 412 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); |
| @@ -412,15 +418,24 @@ int CmdRecoveryAudit() | |||
| 412 | if (!audit.fDeriveComplete) | 418 | if (!audit.fDeriveComplete) |
| 413 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); | 419 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); |
| 414 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); | 420 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); |
| 421 | if (audit.fSeedEncryptedLocked) | ||
| 422 | { | ||
| 423 | printf(" recovery coverage: unavailable while wallet is locked\n"); | ||
| 424 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | ||
| 425 | printf("\n"); | ||
| 426 | printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n"); | ||
| 427 | fflush(stdout); | ||
| 428 | return 2; | ||
| 429 | } | ||
| 415 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", | 430 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", |
| 416 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); | 431 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); |
| 417 | printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", | 432 | printf(" not phrase-backed balance: %s BTF (%d transaction(s))\n", |
| 418 | FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); | 433 | FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); |
| 419 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | 434 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); |
| 420 | printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", | 435 | printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", |
| 421 | FormatMoney(audit.nRecoverableImmatureCredit).c_str(), | 436 | FormatMoney(audit.nRecoverableImmatureCredit).c_str(), |
| 422 | audit.nRecoverableImmatureTx); | 437 | audit.nRecoverableImmatureTx); |
| 423 | printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n", | 438 | printf(" not phrase-backed immature: %s BTF (%d transaction(s))\n", |
| 424 | FormatMoney(audit.nLegacyImmatureCredit).c_str(), | 439 | FormatMoney(audit.nLegacyImmatureCredit).c_str(), |
| 425 | audit.nLegacyImmatureTx); | 440 | audit.nLegacyImmatureTx); |
| 426 | 441 | ||
| @@ -442,7 +457,7 @@ int CmdRecoveryAudit() | |||
| 442 | { | 457 | { |
| 443 | printf("\n"); | 458 | printf("\n"); |
| 444 | printf("Warning: some coins are on keys the phrase does not reproduce.\n"); | 459 | printf("Warning: some coins are on keys the phrase does not reproduce.\n"); |
| 445 | printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n"); | 460 | printf("Keep a file backup of this wallet until that balance has been moved to a phrase-backed address.\n"); |
| 446 | fflush(stdout); | 461 | fflush(stdout); |
| 447 | return 2; | 462 | return 2; |
| 448 | } | 463 | } |
| @@ -456,10 +471,952 @@ int CmdRecoveryAudit() | |||
| 456 | return 0; | 471 | return 0; |
| 457 | } | 472 | } |
| 458 | 473 | ||
| 474 | struct WalletStorageAuditCounts | ||
| 475 | { | ||
| 476 | unsigned int nTotal = 0; | ||
| 477 | unsigned int nMalformed = 0; | ||
| 478 | unsigned int nUnknown = 0; | ||
| 479 | unsigned int nVersion = 0; | ||
| 480 | unsigned int nNames = 0; | ||
| 481 | unsigned int nTransactions = 0; | ||
| 482 | unsigned int nPlainKeys = 0; | ||
| 483 | unsigned int nEncryptedKeys = 0; | ||
| 484 | unsigned int nMasterKeys = 0; | ||
| 485 | unsigned int nDefaultKey = 0; | ||
| 486 | unsigned int nPlainHDMaster = 0; | ||
| 487 | unsigned int nPlainHDChainCode = 0; | ||
| 488 | unsigned int nCryptedHDMaster = 0; | ||
| 489 | unsigned int nCryptedHDChainCode = 0; | ||
| 490 | unsigned int nWalletMinVersion = 0; | ||
| 491 | unsigned int nPool = 0; | ||
| 492 | unsigned int nSettings = 0; | ||
| 493 | unsigned int nHDNext = 0; | ||
| 494 | unsigned int nHDSchema = 0; | ||
| 495 | unsigned int nHDCoinType = 0; | ||
| 496 | unsigned int nHDReceiveNext = 0; | ||
| 497 | unsigned int nHDChangeNext = 0; | ||
| 498 | }; | ||
| 499 | |||
| 500 | static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain) | ||
| 501 | { | ||
| 502 | if (nMaster == 0 && nChain == 0) | ||
| 503 | return "none"; | ||
| 504 | if (nMaster > 0 && nChain > 0) | ||
| 505 | return "complete"; | ||
| 506 | return "incomplete"; | ||
| 507 | } | ||
| 508 | |||
| 509 | static void CountWalletStorageType(const std::string& strType, | ||
| 510 | WalletStorageAuditCounts& c) | ||
| 511 | { | ||
| 512 | if (strType == "version") | ||
| 513 | c.nVersion++; | ||
| 514 | else if (strType == "name") | ||
| 515 | c.nNames++; | ||
| 516 | else if (strType == "tx") | ||
| 517 | c.nTransactions++; | ||
| 518 | else if (strType == "key") | ||
| 519 | c.nPlainKeys++; | ||
| 520 | else if (strType == "ckey") | ||
| 521 | c.nEncryptedKeys++; | ||
| 522 | else if (strType == "mkey") | ||
| 523 | c.nMasterKeys++; | ||
| 524 | else if (strType == "defaultkey") | ||
| 525 | c.nDefaultKey++; | ||
| 526 | else if (strType == "hdmaster") | ||
| 527 | c.nPlainHDMaster++; | ||
| 528 | else if (strType == "hdchaincode") | ||
| 529 | c.nPlainHDChainCode++; | ||
| 530 | else if (strType == "cryptedhdmaster") | ||
| 531 | c.nCryptedHDMaster++; | ||
| 532 | else if (strType == "cryptedhdchaincode") | ||
| 533 | c.nCryptedHDChainCode++; | ||
| 534 | else if (strType == "walletminversion") | ||
| 535 | c.nWalletMinVersion++; | ||
| 536 | else if (strType == "pool") | ||
| 537 | c.nPool++; | ||
| 538 | else if (strType == "setting") | ||
| 539 | c.nSettings++; | ||
| 540 | else if (strType == "hdnext") | ||
| 541 | c.nHDNext++; | ||
| 542 | else if (strType == "hdschema") | ||
| 543 | c.nHDSchema++; | ||
| 544 | else if (strType == "hdcointype") | ||
| 545 | c.nHDCoinType++; | ||
| 546 | else if (strType == "hdreceivenext") | ||
| 547 | c.nHDReceiveNext++; | ||
| 548 | else if (strType == "hdchangenext") | ||
| 549 | c.nHDChangeNext++; | ||
| 550 | else | ||
| 551 | c.nUnknown++; | ||
| 552 | } | ||
| 553 | |||
| 554 | static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts, | ||
| 555 | std::string& strError) | ||
| 556 | { | ||
| 557 | class CWalletStorageCountVisitor : public CWalletRecordVisitor | ||
| 558 | { | ||
| 559 | public: | ||
| 560 | WalletStorageAuditCounts& counts; | ||
| 561 | |||
| 562 | explicit CWalletStorageCountVisitor(WalletStorageAuditCounts& countsIn) | ||
| 563 | : counts(countsIn) { } | ||
| 564 | |||
| 565 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 566 | const CDataStream& ssValue, | ||
| 567 | std::string& strErrorRet) | ||
| 568 | { | ||
| 569 | (void)ssValue; | ||
| 570 | (void)strErrorRet; | ||
| 571 | counts.nTotal++; | ||
| 572 | try | ||
| 573 | { | ||
| 574 | CDataStream ssKey = ssKeyIn; | ||
| 575 | std::string strType; | ||
| 576 | ssKey >> strType; | ||
| 577 | if (ssKey.fail()) | ||
| 578 | { | ||
| 579 | counts.nMalformed++; | ||
| 580 | return true; | ||
| 581 | } | ||
| 582 | CountWalletStorageType(strType, counts); | ||
| 583 | } | ||
| 584 | catch (...) | ||
| 585 | { | ||
| 586 | counts.nMalformed++; | ||
| 587 | } | ||
| 588 | return true; | ||
| 589 | } | ||
| 590 | }; | ||
| 591 | |||
| 592 | CWalletStorageCountVisitor visitor(counts); | ||
| 593 | return ScanWalletRecords(visitor, strError); | ||
| 594 | } | ||
| 595 | |||
| 596 | static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts) | ||
| 597 | { | ||
| 598 | std::string strJson; | ||
| 599 | strJson += "{\n"; | ||
| 600 | strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n"; | ||
| 601 | strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal); | ||
| 602 | strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed); | ||
| 603 | strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown); | ||
| 604 | strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys); | ||
| 605 | strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys); | ||
| 606 | strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys); | ||
| 607 | strJson += strprintf(" \"default_public_key\": \"%s\",\n", | ||
| 608 | counts.nDefaultKey ? "present" : "none"); | ||
| 609 | strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n", | ||
| 610 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 611 | counts.nPlainHDChainCode).c_str()); | ||
| 612 | strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n", | ||
| 613 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 614 | counts.nCryptedHDChainCode).c_str()); | ||
| 615 | strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n", | ||
| 616 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 617 | strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool); | ||
| 618 | strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions); | ||
| 619 | strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames); | ||
| 620 | strJson += strprintf(" \"settings\": %u,\n", counts.nSettings); | ||
| 621 | strJson += strprintf(" \"hd_metadata_records\": %u,\n", | ||
| 622 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 623 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 624 | strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion); | ||
| 625 | strJson += "}\n"; | ||
| 626 | return strJson; | ||
| 627 | } | ||
| 628 | |||
| 629 | static bool WriteAuditTextFile(const std::string& strPath, | ||
| 630 | const std::string& strText, | ||
| 631 | std::string& strError) | ||
| 632 | { | ||
| 633 | FILE* pf = fopen(strPath.c_str(), "wb"); | ||
| 634 | if (!pf) | ||
| 635 | { | ||
| 636 | strError = "cannot open output file"; | ||
| 637 | return false; | ||
| 638 | } | ||
| 639 | size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf); | ||
| 640 | bool fCloseOk = fclose(pf) == 0; | ||
| 641 | if (nWritten != strText.size() || !fCloseOk) | ||
| 642 | { | ||
| 643 | strError = "could not write the complete output file"; | ||
| 644 | return false; | ||
| 645 | } | ||
| 646 | return true; | ||
| 647 | } | ||
| 648 | |||
| 649 | static void AddWalletStorageFailure(std::vector<std::string>& vFailures, | ||
| 650 | const std::string& strFailure) | ||
| 651 | { | ||
| 652 | vFailures.push_back(strFailure); | ||
| 653 | } | ||
| 654 | |||
| 655 | static void BuildWalletStorageSanityFailures(const WalletStorageAuditCounts& counts, | ||
| 656 | std::vector<std::string>& vFailures) | ||
| 657 | { | ||
| 658 | bool fPlainHD = counts.nPlainHDMaster > 0 || counts.nPlainHDChainCode > 0; | ||
| 659 | bool fCryptedHD = counts.nCryptedHDMaster > 0 || counts.nCryptedHDChainCode > 0; | ||
| 660 | bool fEncryptedRecords = counts.nEncryptedKeys > 0 || | ||
| 661 | counts.nMasterKeys > 0 || | ||
| 662 | fCryptedHD; | ||
| 663 | |||
| 664 | if (counts.nMalformed > 0) | ||
| 665 | AddWalletStorageFailure(vFailures, "wallet.dat contains malformed records"); | ||
| 666 | if (counts.nUnknown > 0) | ||
| 667 | AddWalletStorageFailure(vFailures, "wallet.dat contains unknown records"); | ||
| 668 | if ((counts.nPlainHDMaster > 0) != (counts.nPlainHDChainCode > 0)) | ||
| 669 | AddWalletStorageFailure(vFailures, "plain HD seed is incomplete"); | ||
| 670 | if ((counts.nCryptedHDMaster > 0) != (counts.nCryptedHDChainCode > 0)) | ||
| 671 | AddWalletStorageFailure(vFailures, "encrypted HD seed is incomplete"); | ||
| 672 | if (fPlainHD && fCryptedHD) | ||
| 673 | AddWalletStorageFailure(vFailures, "wallet.dat contains both plain and encrypted HD seed records"); | ||
| 674 | if (counts.nDefaultKey > 1) | ||
| 675 | AddWalletStorageFailure(vFailures, "wallet.dat contains more than one default public key record"); | ||
| 676 | if (counts.nWalletMinVersion > 1) | ||
| 677 | AddWalletStorageFailure(vFailures, "wallet.dat contains more than one minimum-version record"); | ||
| 678 | |||
| 679 | if (fEncryptedRecords) | ||
| 680 | { | ||
| 681 | if (counts.nPlainKeys > 0) | ||
| 682 | AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain private key records"); | ||
| 683 | if (fPlainHD) | ||
| 684 | AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain HD seed records"); | ||
| 685 | if (counts.nMasterKeys == 0) | ||
| 686 | AddWalletStorageFailure(vFailures, "encrypted wallet has no encryption master key record"); | ||
| 687 | if (counts.nWalletMinVersion == 0) | ||
| 688 | AddWalletStorageFailure(vFailures, "encrypted wallet has no minimum-version record"); | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | int CmdWalletStorageCheck() | ||
| 693 | { | ||
| 694 | AttachTerminal(); | ||
| 695 | |||
| 696 | WalletStorageAuditCounts counts; | ||
| 697 | std::string strError; | ||
| 698 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 699 | { | ||
| 700 | fprintf(stderr, "Cannot check wallet storage: %s\n", strError.c_str()); | ||
| 701 | return 1; | ||
| 702 | } | ||
| 703 | |||
| 704 | std::vector<std::string> vFailures; | ||
| 705 | BuildWalletStorageSanityFailures(counts, vFailures); | ||
| 706 | |||
| 707 | printf("Wallet storage sanity check\n"); | ||
| 708 | printf(" records total: %u\n", counts.nTotal); | ||
| 709 | printf(" encrypted records: %s\n", | ||
| 710 | (counts.nEncryptedKeys || counts.nMasterKeys || | ||
| 711 | counts.nCryptedHDMaster || counts.nCryptedHDChainCode) ? "yes" : "no"); | ||
| 712 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 713 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 714 | printf(" plain HD seed: %s\n", | ||
| 715 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 716 | counts.nPlainHDChainCode).c_str()); | ||
| 717 | printf(" encrypted HD seed: %s\n", | ||
| 718 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 719 | counts.nCryptedHDChainCode).c_str()); | ||
| 720 | if (vFailures.empty()) | ||
| 721 | { | ||
| 722 | printf(" storage sanity: ok\n"); | ||
| 723 | fflush(stdout); | ||
| 724 | return 0; | ||
| 725 | } | ||
| 726 | |||
| 727 | printf(" storage sanity: failed\n"); | ||
| 728 | for (std::vector<std::string>::const_iterator it = vFailures.begin(); | ||
| 729 | it != vFailures.end(); ++it) | ||
| 730 | printf(" failure: %s\n", it->c_str()); | ||
| 731 | fflush(stdout); | ||
| 732 | return 2; | ||
| 733 | } | ||
| 734 | |||
| 735 | int CmdWalletStorageAudit(const std::string& strJsonOut) | ||
| 736 | { | ||
| 737 | AttachTerminal(); | ||
| 738 | |||
| 739 | WalletStorageAuditCounts counts; | ||
| 740 | std::string strError; | ||
| 741 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 742 | { | ||
| 743 | fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str()); | ||
| 744 | return 1; | ||
| 745 | } | ||
| 746 | |||
| 747 | if (!strJsonOut.empty()) | ||
| 748 | { | ||
| 749 | std::string strWriteError; | ||
| 750 | if (!WriteAuditTextFile(strJsonOut, | ||
| 751 | WalletStorageAuditJson(counts), | ||
| 752 | strWriteError)) | ||
| 753 | { | ||
| 754 | fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n", | ||
| 755 | strWriteError.c_str()); | ||
| 756 | return 1; | ||
| 757 | } | ||
| 758 | printf("Wallet storage audit written to %s\n", strJsonOut.c_str()); | ||
| 759 | fflush(stdout); | ||
| 760 | return counts.nMalformed > 0 ? 2 : 0; | ||
| 761 | } | ||
| 762 | |||
| 763 | printf("Wallet storage audit\n"); | ||
| 764 | printf(" records total: %u\n", counts.nTotal); | ||
| 765 | printf(" malformed records: %u\n", counts.nMalformed); | ||
| 766 | printf(" unknown records: %u\n", counts.nUnknown); | ||
| 767 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 768 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 769 | printf(" encryption master keys: %u\n", counts.nMasterKeys); | ||
| 770 | printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none"); | ||
| 771 | printf(" plain HD seed: %s\n", | ||
| 772 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 773 | counts.nPlainHDChainCode).c_str()); | ||
| 774 | printf(" encrypted HD seed: %s\n", | ||
| 775 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 776 | counts.nCryptedHDChainCode).c_str()); | ||
| 777 | printf(" wallet minimum version: %s\n", | ||
| 778 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 779 | printf(" keypool entries: %u\n", counts.nPool); | ||
| 780 | printf(" wallet transactions: %u\n", counts.nTransactions); | ||
| 781 | printf(" address book labels: %u\n", counts.nNames); | ||
| 782 | printf(" settings: %u\n", counts.nSettings); | ||
| 783 | printf(" HD metadata records: %u\n", | ||
| 784 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 785 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 786 | printf(" database version records: %u\n", counts.nVersion); | ||
| 787 | fflush(stdout); | ||
| 788 | |||
| 789 | if (counts.nMalformed > 0) | ||
| 790 | return 2; | ||
| 791 | return 0; | ||
| 792 | } | ||
| 793 | |||
| 794 | static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss) | ||
| 795 | { | ||
| 796 | return vector<unsigned char>(ss.begin(), ss.end()); | ||
| 797 | } | ||
| 798 | |||
| 799 | namespace { | ||
| 800 | |||
| 801 | static void RemoveSQLiteExportFiles(const string& strPath) | ||
| 802 | { | ||
| 803 | remove(strPath.c_str()); | ||
| 804 | remove((strPath + "-wal").c_str()); | ||
| 805 | remove((strPath + "-shm").c_str()); | ||
| 806 | } | ||
| 807 | |||
| 808 | static void RemoveFailedRestoreWallet(const string& strWalletPath) | ||
| 809 | { | ||
| 810 | DBFlush(true); | ||
| 811 | remove(strWalletPath.c_str()); | ||
| 812 | } | ||
| 813 | |||
| 814 | class CWalletSQLiteExportVisitor : public CWalletRecordVisitor | ||
| 815 | { | ||
| 816 | public: | ||
| 817 | CWalletDBSQLite& db; | ||
| 818 | int& nCopied; | ||
| 819 | |||
| 820 | CWalletSQLiteExportVisitor(CWalletDBSQLite& dbIn, int& nCopiedIn) | ||
| 821 | : db(dbIn), nCopied(nCopiedIn) { } | ||
| 822 | |||
| 823 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 824 | const CDataStream& ssValue, | ||
| 825 | string& strErrorRet) | ||
| 826 | { | ||
| 827 | if (!db.WriteRecord(DataStreamBytesLocal(ssKey), | ||
| 828 | DataStreamBytesLocal(ssValue), | ||
| 829 | strErrorRet)) | ||
| 830 | return false; | ||
| 831 | nCopied++; | ||
| 832 | return true; | ||
| 833 | } | ||
| 834 | }; | ||
| 835 | |||
| 836 | class CWalletRecordMapVisitor : public CWalletRecordVisitor | ||
| 837 | { | ||
| 838 | public: | ||
| 839 | std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords; | ||
| 840 | |||
| 841 | explicit CWalletRecordMapVisitor( | ||
| 842 | std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn) | ||
| 843 | : mapRecords(mapRecordsIn) { } | ||
| 844 | |||
| 845 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 846 | const CDataStream& ssValue, | ||
| 847 | string& strErrorRet) | ||
| 848 | { | ||
| 849 | vector<unsigned char> vchKey = DataStreamBytesLocal(ssKey); | ||
| 850 | if (mapRecords.count(vchKey)) | ||
| 851 | { | ||
| 852 | strErrorRet = "duplicate serialized wallet key"; | ||
| 853 | return false; | ||
| 854 | } | ||
| 855 | mapRecords[vchKey] = DataStreamBytesLocal(ssValue); | ||
| 856 | return true; | ||
| 857 | } | ||
| 858 | }; | ||
| 859 | |||
| 860 | class CWalletRawRestoreDB : public CWalletDB | ||
| 861 | { | ||
| 862 | public: | ||
| 863 | CWalletRawRestoreDB(const char* pszMode="cr+", bool fTxn=true) | ||
| 864 | : CWalletDB(pszMode, fTxn) { } | ||
| 865 | |||
| 866 | bool WriteRaw(CDataStream ssKey, CDataStream ssValue) | ||
| 867 | { | ||
| 868 | if (!pdb || ssKey.empty()) | ||
| 869 | return false; | ||
| 870 | |||
| 871 | Dbt datKey((void*)&ssKey[0], ssKey.size()); | ||
| 872 | Dbt datValue(ssValue.empty() ? NULL : (void*)&ssValue[0], | ||
| 873 | ssValue.size()); | ||
| 874 | int ret = pdb->put(GetTxn(), &datKey, &datValue, 0); | ||
| 875 | |||
| 876 | memset(datKey.get_data(), 0, datKey.get_size()); | ||
| 877 | if (datValue.get_data()) | ||
| 878 | memset(datValue.get_data(), 0, datValue.get_size()); | ||
| 879 | return ret == 0; | ||
| 880 | } | ||
| 881 | }; | ||
| 882 | |||
| 883 | class CWalletSQLiteRestoreVisitor : public CWalletRecordVisitor | ||
| 884 | { | ||
| 885 | public: | ||
| 886 | CWalletRawRestoreDB& db; | ||
| 887 | int& nCopied; | ||
| 888 | |||
| 889 | CWalletSQLiteRestoreVisitor(CWalletRawRestoreDB& dbIn, int& nCopiedIn) | ||
| 890 | : db(dbIn), nCopied(nCopiedIn) { } | ||
| 891 | |||
| 892 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 893 | const CDataStream& ssValue, | ||
| 894 | string& strErrorRet) | ||
| 895 | { | ||
| 896 | if (!db.WriteRaw(ssKey, ssValue)) | ||
| 897 | { | ||
| 898 | strErrorRet = "could not write raw wallet record"; | ||
| 899 | return false; | ||
| 900 | } | ||
| 901 | nCopied++; | ||
| 902 | return true; | ||
| 903 | } | ||
| 904 | }; | ||
| 905 | |||
| 906 | class CWalletSQLiteLoadCheckVisitor : public CWalletRecordVisitor | ||
| 907 | { | ||
| 908 | public: | ||
| 909 | int nRecords; | ||
| 910 | int nUnknown; | ||
| 911 | string strFailure; | ||
| 912 | |||
| 913 | CWalletSQLiteLoadCheckVisitor() : nRecords(0), nUnknown(0) { } | ||
| 914 | |||
| 915 | bool Fail(const string& strMessage, string& strErrorRet) | ||
| 916 | { | ||
| 917 | strFailure = strMessage; | ||
| 918 | strErrorRet = strMessage; | ||
| 919 | return false; | ||
| 920 | } | ||
| 921 | |||
| 922 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 923 | const CDataStream& ssValueIn, | ||
| 924 | string& strErrorRet) | ||
| 925 | { | ||
| 926 | nRecords++; | ||
| 927 | try | ||
| 928 | { | ||
| 929 | CDataStream ssKey = ssKeyIn; | ||
| 930 | CDataStream ssValue = ssValueIn; | ||
| 931 | string strType; | ||
| 932 | ssKey >> strType; | ||
| 933 | if (ssKey.fail()) | ||
| 934 | return Fail("malformed wallet record key", strErrorRet); | ||
| 935 | |||
| 936 | if (strType == "name") | ||
| 937 | { | ||
| 938 | string strAddress; | ||
| 939 | string strName; | ||
| 940 | ssKey >> strAddress; | ||
| 941 | ssValue >> strName; | ||
| 942 | } | ||
| 943 | else if (strType == "tx") | ||
| 944 | { | ||
| 945 | uint256 hash; | ||
| 946 | CWalletTx wtx; | ||
| 947 | ssKey >> hash; | ||
| 948 | ssValue >> wtx; | ||
| 949 | } | ||
| 950 | else if (strType == "key") | ||
| 951 | { | ||
| 952 | vector<unsigned char> vchPubKey; | ||
| 953 | CPrivKey vchPrivKey; | ||
| 954 | ssKey >> vchPubKey; | ||
| 955 | ssValue >> vchPrivKey; | ||
| 956 | } | ||
| 957 | else if (strType == "mkey") | ||
| 958 | { | ||
| 959 | unsigned int nID = 0; | ||
| 960 | CWalletMasterKey kMasterKey; | ||
| 961 | ssKey >> nID; | ||
| 962 | ssValue >> kMasterKey; | ||
| 963 | } | ||
| 964 | else if (strType == "ckey") | ||
| 965 | { | ||
| 966 | vector<unsigned char> vchPubKey; | ||
| 967 | vector<unsigned char> vchCryptedSecret; | ||
| 968 | ssKey >> vchPubKey; | ||
| 969 | ssValue >> vchCryptedSecret; | ||
| 970 | } | ||
| 971 | else if (strType == "defaultkey" || | ||
| 972 | strType == "hdmaster" || | ||
| 973 | strType == "hdchaincode" || | ||
| 974 | strType == "cryptedhdmaster" || | ||
| 975 | strType == "cryptedhdchaincode") | ||
| 976 | { | ||
| 977 | vector<unsigned char> vch; | ||
| 978 | ssValue >> vch; | ||
| 979 | } | ||
| 980 | else if (strType == "hdnext" || | ||
| 981 | strType == "hdreceivenext" || | ||
| 982 | strType == "hdchangenext" || | ||
| 983 | strType == "hdcointype") | ||
| 984 | { | ||
| 985 | unsigned int nValue = 0; | ||
| 986 | ssValue >> nValue; | ||
| 987 | } | ||
| 988 | else if (strType == "hdschema") | ||
| 989 | { | ||
| 990 | int nSchema = 0; | ||
| 991 | ssValue >> nSchema; | ||
| 992 | if (!ssValue.fail() && | ||
| 993 | nSchema != HD_SCHEMA_NONE && | ||
| 994 | nSchema != HD_SCHEMA_LEGACY && | ||
| 995 | nSchema != HD_SCHEMA_BIP44) | ||
| 996 | return Fail("unsupported deterministic wallet schema", | ||
| 997 | strErrorRet); | ||
| 998 | } | ||
| 999 | else if (strType == "walletminversion") | ||
| 1000 | { | ||
| 1001 | int nMinVersion = 0; | ||
| 1002 | ssValue >> nMinVersion; | ||
| 1003 | if (!ssValue.fail() && nMinVersion > WALLET_FORMAT_SUPPORTED) | ||
| 1004 | return Fail("unsupported future wallet format", strErrorRet); | ||
| 1005 | } | ||
| 1006 | else if (strType == "pool") | ||
| 1007 | { | ||
| 1008 | int64 nIndex = 0; | ||
| 1009 | vector<unsigned char> vchPubKey; | ||
| 1010 | ssKey >> nIndex; | ||
| 1011 | ssValue >> vchPubKey; | ||
| 1012 | } | ||
| 1013 | else if (strType == "setting") | ||
| 1014 | { | ||
| 1015 | string strKey; | ||
| 1016 | ssKey >> strKey; | ||
| 1017 | if (strKey == "nTransactionFee") | ||
| 1018 | { | ||
| 1019 | int64 nValue = 0; | ||
| 1020 | ssValue >> nValue; | ||
| 1021 | } | ||
| 1022 | else if (strKey == "addrIncoming") | ||
| 1023 | { | ||
| 1024 | CAddress addr; | ||
| 1025 | ssValue >> addr; | ||
| 1026 | } | ||
| 1027 | else if (strKey == "nMineMode") | ||
| 1028 | { | ||
| 1029 | int nValue = 0; | ||
| 1030 | ssValue >> nValue; | ||
| 1031 | } | ||
| 1032 | else if (strKey == "fGenerateBitcoins") | ||
| 1033 | { | ||
| 1034 | int fValue = 0; | ||
| 1035 | ssValue >> fValue; | ||
| 1036 | } | ||
| 1037 | else if (strKey == "strParticipantPool") | ||
| 1038 | { | ||
| 1039 | string strValue; | ||
| 1040 | ssValue >> strValue; | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | else if (strType == "version") | ||
| 1044 | { | ||
| 1045 | int nVersion = 0; | ||
| 1046 | ssValue >> nVersion; | ||
| 1047 | } | ||
| 1048 | else | ||
| 1049 | { | ||
| 1050 | nUnknown++; | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | if (ssKey.fail()) | ||
| 1054 | return Fail("malformed wallet record key", strErrorRet); | ||
| 1055 | if (ssValue.fail()) | ||
| 1056 | return Fail("malformed wallet record value", strErrorRet); | ||
| 1057 | } | ||
| 1058 | catch (const std::exception& e) | ||
| 1059 | { | ||
| 1060 | return Fail(strprintf("wallet record parse exception: %s", e.what()), | ||
| 1061 | strErrorRet); | ||
| 1062 | } | ||
| 1063 | catch (...) | ||
| 1064 | { | ||
| 1065 | return Fail("wallet record parse exception", strErrorRet); | ||
| 1066 | } | ||
| 1067 | return true; | ||
| 1068 | } | ||
| 1069 | }; | ||
| 1070 | |||
| 1071 | } // namespace | ||
| 1072 | |||
| 1073 | // Export the currently-loaded wallet to a SQLite file. Shared by the | ||
| 1074 | // -walletsqliteexport command and the GUI conversion wizard: opens the | ||
| 1075 | // destination, copies every record inside one transaction, checkpoints, and | ||
| 1076 | // verifies the stored count matches. Returns false with strErrorRet set on any | ||
| 1077 | // failure and leaves no partial file behind; the source wallet is only read. | ||
| 1078 | bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet, | ||
| 1079 | std::string& strErrorRet) | ||
| 1080 | { | ||
| 1081 | nCopiedRet = 0; | ||
| 1082 | if (strDest.empty()) | ||
| 1083 | { | ||
| 1084 | strErrorRet = "no SQLite export path was given"; | ||
| 1085 | return false; | ||
| 1086 | } | ||
| 1087 | if (FileExists(strDest.c_str())) | ||
| 1088 | { | ||
| 1089 | strErrorRet = strprintf("refusing to overwrite existing file: %s", strDest.c_str()); | ||
| 1090 | return false; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | CWalletDBSQLite db; | ||
| 1094 | if (!db.Open(strDest, strErrorRet)) | ||
| 1095 | { | ||
| 1096 | RemoveSQLiteExportFiles(strDest); | ||
| 1097 | return false; | ||
| 1098 | } | ||
| 1099 | if (!db.BeginTransaction(strErrorRet)) | ||
| 1100 | { | ||
| 1101 | db.Close(); | ||
| 1102 | RemoveSQLiteExportFiles(strDest); | ||
| 1103 | return false; | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | CWalletSQLiteExportVisitor visitor(db, nCopiedRet); | ||
| 1107 | if (!ScanWalletRecords(visitor, strErrorRet)) | ||
| 1108 | { | ||
| 1109 | string strRollbackError; | ||
| 1110 | db.RollbackTransaction(strRollbackError); | ||
| 1111 | db.Close(); | ||
| 1112 | RemoveSQLiteExportFiles(strDest); | ||
| 1113 | return false; | ||
| 1114 | } | ||
| 1115 | if (!db.CommitTransaction(strErrorRet) || !db.Checkpoint(strErrorRet)) | ||
| 1116 | { | ||
| 1117 | db.Close(); | ||
| 1118 | RemoveSQLiteExportFiles(strDest); | ||
| 1119 | return false; | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | int nCount = 0; | ||
| 1123 | if (!db.CountRecords(nCount, strErrorRet)) | ||
| 1124 | { | ||
| 1125 | db.Close(); | ||
| 1126 | RemoveSQLiteExportFiles(strDest); | ||
| 1127 | return false; | ||
| 1128 | } | ||
| 1129 | if (nCount != nCopiedRet) | ||
| 1130 | { | ||
| 1131 | strErrorRet = strprintf("export count mismatch: copied %d, stored %d", | ||
| 1132 | nCopiedRet, nCount); | ||
| 1133 | db.Close(); | ||
| 1134 | RemoveSQLiteExportFiles(strDest); | ||
| 1135 | return false; | ||
| 1136 | } | ||
| 1137 | db.Close(); | ||
| 1138 | return true; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | // Convert the running Berkeley DB wallet to SQLite for the GUI wizard: export | ||
| 1142 | // wallet.dat to <datadir>/wallet.sqlite, then record the backend marker so the | ||
| 1143 | // next start opens SQLite. wallet.dat is never modified -- it stays as the | ||
| 1144 | // fallback. Refuses if already on SQLite or if a wallet.sqlite already exists. | ||
| 1145 | bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet) | ||
| 1146 | { | ||
| 1147 | nCopiedRet = 0; | ||
| 1148 | if (WalletSQLiteRuntimeActive()) | ||
| 1149 | { | ||
| 1150 | strErrorRet = "this wallet is already on the SQLite backend"; | ||
| 1151 | return false; | ||
| 1152 | } | ||
| 1153 | string strDest = GetAppDir() + "/wallet.sqlite"; | ||
| 1154 | if (FileExists(strDest.c_str())) | ||
| 1155 | { | ||
| 1156 | strErrorRet = strprintf("a SQLite wallet already exists at %s", strDest.c_str()); | ||
| 1157 | return false; | ||
| 1158 | } | ||
| 1159 | if (!ExportActiveWalletToSQLite(strDest, nCopiedRet, strErrorRet)) | ||
| 1160 | return false; | ||
| 1161 | if (!WriteWalletBackendMarker("sqlite")) | ||
| 1162 | { | ||
| 1163 | strErrorRet = "the wallet was exported but the backend choice could not be saved"; | ||
| 1164 | return false; | ||
| 1165 | } | ||
| 1166 | return true; | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | int CmdWalletSQLiteExport(const std::string& strDest) | ||
| 1170 | { | ||
| 1171 | AttachTerminal(); | ||
| 1172 | |||
| 1173 | // Keep the command's own specific messages for the two conditions the | ||
| 1174 | // shared helper folds into a generic error, so scripts and the tests that | ||
| 1175 | // read this output see the same wording as before. | ||
| 1176 | if (strDest.empty()) | ||
| 1177 | { | ||
| 1178 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1179 | return 1; | ||
| 1180 | } | ||
| 1181 | if (FileExists(strDest.c_str())) | ||
| 1182 | { | ||
| 1183 | fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n", | ||
| 1184 | strDest.c_str()); | ||
| 1185 | return 1; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | int nCopied = 0; | ||
| 1189 | string strError; | ||
| 1190 | if (!ExportActiveWalletToSQLite(strDest, nCopied, strError)) | ||
| 1191 | { | ||
| 1192 | fprintf(stderr, "Cannot export wallet to SQLite: %s\n", strError.c_str()); | ||
| 1193 | return 1; | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | printf("SQLite wallet export written to %s\n", strDest.c_str()); | ||
| 1197 | printf(" records copied: %d\n", nCopied); | ||
| 1198 | fflush(stdout); | ||
| 1199 | return 0; | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | int CmdWalletSQLiteVerify(const std::string& strPath) | ||
| 1203 | { | ||
| 1204 | AttachTerminal(); | ||
| 1205 | |||
| 1206 | if (strPath.empty()) | ||
| 1207 | { | ||
| 1208 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1209 | return 1; | ||
| 1210 | } | ||
| 1211 | if (!FileExists(strPath.c_str())) | ||
| 1212 | { | ||
| 1213 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1214 | strPath.c_str()); | ||
| 1215 | return 1; | ||
| 1216 | } | ||
| 1217 | |||
| 1218 | std::map<vector<unsigned char>, vector<unsigned char> > mapBDB; | ||
| 1219 | std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite; | ||
| 1220 | string strError; | ||
| 1221 | |||
| 1222 | CWalletRecordMapVisitor bdbVisitor(mapBDB); | ||
| 1223 | if (!ScanWalletRecords(bdbVisitor, strError)) | ||
| 1224 | { | ||
| 1225 | fprintf(stderr, "Cannot scan wallet.dat records: %s\n", strError.c_str()); | ||
| 1226 | return 1; | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | CWalletDBSQLite db; | ||
| 1230 | if (!db.Open(strPath, strError)) | ||
| 1231 | { | ||
| 1232 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1233 | return 1; | ||
| 1234 | } | ||
| 1235 | CWalletRecordMapVisitor sqliteVisitor(mapSQLite); | ||
| 1236 | if (!db.ScanRecords(sqliteVisitor, strError)) | ||
| 1237 | { | ||
| 1238 | fprintf(stderr, "Cannot scan SQLite wallet export: %s\n", strError.c_str()); | ||
| 1239 | return 1; | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | int nMissing = 0; | ||
| 1243 | int nMismatched = 0; | ||
| 1244 | for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it = | ||
| 1245 | mapBDB.begin(); it != mapBDB.end(); ++it) | ||
| 1246 | { | ||
| 1247 | std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator sit = | ||
| 1248 | mapSQLite.find(it->first); | ||
| 1249 | if (sit == mapSQLite.end()) | ||
| 1250 | nMissing++; | ||
| 1251 | else if (sit->second != it->second) | ||
| 1252 | nMismatched++; | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | int nExtra = 0; | ||
| 1256 | for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it = | ||
| 1257 | mapSQLite.begin(); it != mapSQLite.end(); ++it) | ||
| 1258 | { | ||
| 1259 | if (!mapBDB.count(it->first)) | ||
| 1260 | nExtra++; | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | printf("SQLite wallet export verification\n"); | ||
| 1264 | printf(" wallet.dat records: %u\n", (unsigned int)mapBDB.size()); | ||
| 1265 | printf(" SQLite records: %u\n", (unsigned int)mapSQLite.size()); | ||
| 1266 | printf(" missing records: %d\n", nMissing); | ||
| 1267 | printf(" extra records: %d\n", nExtra); | ||
| 1268 | printf(" record value mismatches: %d\n", nMismatched); | ||
| 1269 | if (nMissing == 0 && nExtra == 0 && nMismatched == 0) | ||
| 1270 | { | ||
| 1271 | printf(" verification: ok\n"); | ||
| 1272 | fflush(stdout); | ||
| 1273 | return 0; | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | printf(" verification: failed\n"); | ||
| 1277 | fflush(stdout); | ||
| 1278 | return 2; | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | int CmdWalletSQLiteRestore(const std::string& strPath) | ||
| 1282 | { | ||
| 1283 | AttachTerminal(); | ||
| 1284 | |||
| 1285 | if (strPath.empty()) | ||
| 1286 | { | ||
| 1287 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1288 | return 1; | ||
| 1289 | } | ||
| 1290 | if (!FileExists(strPath.c_str())) | ||
| 1291 | { | ||
| 1292 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1293 | strPath.c_str()); | ||
| 1294 | return 1; | ||
| 1295 | } | ||
| 1296 | |||
| 1297 | string strWalletPath = GetAppDir() + "/wallet.dat"; | ||
| 1298 | if (FileExists(strWalletPath.c_str())) | ||
| 1299 | { | ||
| 1300 | fprintf(stderr, "Refusing to overwrite existing wallet.dat: %s\n", | ||
| 1301 | strWalletPath.c_str()); | ||
| 1302 | return 1; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | string strError; | ||
| 1306 | CWalletDBSQLite sqlite; | ||
| 1307 | if (!sqlite.Open(strPath, strError)) | ||
| 1308 | { | ||
| 1309 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1310 | return 1; | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | int nSQLiteRecords = 0; | ||
| 1314 | if (!sqlite.CountRecords(nSQLiteRecords, strError)) | ||
| 1315 | { | ||
| 1316 | fprintf(stderr, "Cannot count SQLite wallet export records: %s\n", | ||
| 1317 | strError.c_str()); | ||
| 1318 | return 1; | ||
| 1319 | } | ||
| 1320 | if (nSQLiteRecords <= 0) | ||
| 1321 | { | ||
| 1322 | fprintf(stderr, "SQLite wallet export contains no records.\n"); | ||
| 1323 | return 2; | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | CWalletRawRestoreDB bdb("cr+", true); | ||
| 1327 | if (!bdb.TxnBegin()) | ||
| 1328 | { | ||
| 1329 | fprintf(stderr, "Cannot begin Berkeley DB restore transaction.\n"); | ||
| 1330 | return 1; | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | int nCopied = 0; | ||
| 1334 | CWalletSQLiteRestoreVisitor visitor(bdb, nCopied); | ||
| 1335 | if (!sqlite.ScanRecords(visitor, strError)) | ||
| 1336 | { | ||
| 1337 | bdb.TxnAbort(); | ||
| 1338 | bdb.Close(); | ||
| 1339 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1340 | fprintf(stderr, "Cannot restore SQLite wallet export: %s\n", | ||
| 1341 | strError.c_str()); | ||
| 1342 | return 1; | ||
| 1343 | } | ||
| 1344 | if (nCopied != nSQLiteRecords) | ||
| 1345 | { | ||
| 1346 | bdb.TxnAbort(); | ||
| 1347 | bdb.Close(); | ||
| 1348 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1349 | fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n", | ||
| 1350 | nCopied, nSQLiteRecords); | ||
| 1351 | return 2; | ||
| 1352 | } | ||
| 1353 | if (!bdb.TxnCommit()) | ||
| 1354 | { | ||
| 1355 | bdb.Close(); | ||
| 1356 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1357 | fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n"); | ||
| 1358 | return 1; | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | printf("SQLite wallet export restored to wallet.dat\n"); | ||
| 1362 | printf(" records restored: %d\n", nCopied); | ||
| 1363 | fflush(stdout); | ||
| 1364 | return 0; | ||
| 1365 | } | ||
| 1366 | |||
| 1367 | int CmdWalletSQLiteLoadCheck(const std::string& strPath) | ||
| 1368 | { | ||
| 1369 | AttachTerminal(); | ||
| 1370 | |||
| 1371 | if (strPath.empty()) | ||
| 1372 | { | ||
| 1373 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1374 | return 1; | ||
| 1375 | } | ||
| 1376 | if (!FileExists(strPath.c_str())) | ||
| 1377 | { | ||
| 1378 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1379 | strPath.c_str()); | ||
| 1380 | return 1; | ||
| 1381 | } | ||
| 1382 | |||
| 1383 | string strError; | ||
| 1384 | CWalletDBSQLite sqlite; | ||
| 1385 | if (!sqlite.Open(strPath, strError)) | ||
| 1386 | { | ||
| 1387 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1388 | return 1; | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | CWalletSQLiteLoadCheckVisitor visitor; | ||
| 1392 | if (!sqlite.ScanRecords(visitor, strError)) | ||
| 1393 | { | ||
| 1394 | printf("SQLite wallet load check\n"); | ||
| 1395 | printf(" records checked: %d\n", visitor.nRecords); | ||
| 1396 | printf(" unknown records: %d\n", visitor.nUnknown); | ||
| 1397 | printf(" load check: failed\n"); | ||
| 1398 | printf(" failure: %s\n", strError.c_str()); | ||
| 1399 | fflush(stdout); | ||
| 1400 | return 2; | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | printf("SQLite wallet load check\n"); | ||
| 1404 | printf(" records checked: %d\n", visitor.nRecords); | ||
| 1405 | printf(" unknown records: %d\n", visitor.nUnknown); | ||
| 1406 | printf(" load check: ok\n"); | ||
| 1407 | fflush(stdout); | ||
| 1408 | return 0; | ||
| 1409 | } | ||
| 1410 | |||
| 459 | int CmdEncryptWallet(const std::string& strPassphrase) | 1411 | int CmdEncryptWallet(const std::string& strPassphrase) |
| 460 | { | 1412 | { |
| 461 | AttachTerminal(); | 1413 | AttachTerminal(); |
| 462 | 1414 | ||
| 1415 | // Capture the backend now: under SQLite, EncryptWallet() closes the runtime | ||
| 1416 | // store to swap the file, so WalletSQLiteRuntimeActive() reads false by the | ||
| 1417 | // time we would decide whether to purge Berkeley DB logs below. | ||
| 1418 | bool fSQLiteBackend = WalletSQLiteRuntimeActive(); | ||
| 1419 | |||
| 463 | string strBackup; | 1420 | string strBackup; |
| 464 | string strError; | 1421 | string strError; |
| 465 | if (!EncryptWallet(strPassphrase, strBackup, strError)) | 1422 | if (!EncryptWallet(strPassphrase, strBackup, strError)) |
| @@ -470,6 +1427,8 @@ int CmdEncryptWallet(const std::string& strPassphrase) | |||
| 470 | } | 1427 | } |
| 471 | 1428 | ||
| 472 | printf("Wallet encrypted.\n"); | 1429 | printf("Wallet encrypted.\n"); |
| 1430 | if (fSQLiteBackend) | ||
| 1431 | printf("Wallet backend: SQLite (wallet.sqlite).\n"); | ||
| 473 | printf("The old unencrypted wallet was moved to:\n"); | 1432 | printf("The old unencrypted wallet was moved to:\n"); |
| 474 | printf(" %s\n", strBackup.c_str()); | 1433 | printf(" %s\n", strBackup.c_str()); |
| 475 | printf("That backup still contains private keys in plain text. Move it offline or\n"); | 1434 | printf("That backup still contains private keys in plain text. Move it offline or\n"); |
| @@ -482,7 +1441,13 @@ int CmdEncryptWallet(const std::string& strPassphrase) | |||
| 482 | // them. Closing the environment and dropping the logs is the last step of | 1441 | // them. Closing the environment and dropping the logs is the last step of |
| 483 | // encrypting, not an optimisation -- our own backup advice is to copy the | 1442 | // encrypting, not an optimisation -- our own backup advice is to copy the |
| 484 | // whole directory, and following it otherwise carries the keys along. | 1443 | // whole directory, and following it otherwise carries the keys along. |
| 485 | PurgeDbEnvironmentLogs(); | 1444 | // |
| 1445 | // The SQLite backend has no Berkeley DB environment behind the wallet, and | ||
| 1446 | // EncryptWallet() already closed and swapped the SQLite store; there is | ||
| 1447 | // nothing to purge, and calling into the environment here would only open | ||
| 1448 | // one needlessly. | ||
| 1449 | if (!fSQLiteBackend) | ||
| 1450 | PurgeDbEnvironmentLogs(); | ||
| 486 | return 0; | 1451 | return 0; |
| 487 | } | 1452 | } |
| 488 | 1453 | ||