upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/walletcmd.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-01 17:14:12 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-01 17:14:12 +0000
commitb4f0bc693ef7175702f82bcb2110b627922e8cbd (patch)
tree73435feb1a076dfba94d8143d7d7b7784908c82f /src/walletcmd.cpp
parenta1d867ecbcfc9e218408d0a11863a450243a01f9 (diff)
parent6aa3dade77632f13189d2a3ef29897f29a5f9572 (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.cpp975
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
474struct 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
500static 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
509static 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
554static 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
596static 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
629static 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
649static void AddWalletStorageFailure(std::vector<std::string>& vFailures,
650 const std::string& strFailure)
651{
652 vFailures.push_back(strFailure);
653}
654
655static 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
692int 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
735int 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
794static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss)
795{
796 return vector<unsigned char>(ss.begin(), ss.end());
797}
798
799namespace {
800
801static 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
808static void RemoveFailedRestoreWallet(const string& strWalletPath)
809{
810 DBFlush(true);
811 remove(strWalletPath.c_str());
812}
813
814class CWalletSQLiteExportVisitor : public CWalletRecordVisitor
815{
816public:
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
836class CWalletRecordMapVisitor : public CWalletRecordVisitor
837{
838public:
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
860class CWalletRawRestoreDB : public CWalletDB
861{
862public:
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
883class CWalletSQLiteRestoreVisitor : public CWalletRecordVisitor
884{
885public:
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
906class CWalletSQLiteLoadCheckVisitor : public CWalletRecordVisitor
907{
908public:
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.
1078bool 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.
1145bool 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
1169int 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
1202int 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
1281int 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
1367int 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
459int CmdEncryptWallet(const std::string& strPassphrase) 1411int 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