diff options
| author | Bitflash <admin@bitflash.network> | 2026-08-17 10:37:07 -0300 |
|---|---|---|
| committer | Bitflash <admin@bitflash.network> | 2026-08-17 10:37:07 -0300 |
| commit | f7033201689c6eb70f16b242b9f90be548f67aa5 (patch) | |
| tree | e6ecab8a276afb007f341a498dd1a8553a756b0c /src/db.cpp | |
| parent | c56af18e421dff78446399a630b5fa949fc487b3 (diff) | |
| parent | f9ee957bd6d3f28dfee166a82f389aad666a0119 (diff) | |
Merge pull request 'wallet: stage SQLite runtime loading' (#55) from wallet-sqlite-runtime into main
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 361 |
1 files changed, 344 insertions, 17 deletions
| @@ -3,6 +3,7 @@ | |||
| 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. | 3 | // file license.txt or http://www.opensource.org/licenses/mit-license.php. |
| 4 | 4 | ||
| 5 | #include "headers.h" | 5 | #include "headers.h" |
| 6 | #include "wallet_sqlite.h" | ||
| 6 | 7 | ||
| 7 | 8 | ||
| 8 | 9 | ||
| @@ -650,6 +651,289 @@ bool CReviewDB::WriteReviews(uint256 hash, const vector<CReview>& vReviews) | |||
| 650 | // CWalletDB | 651 | // CWalletDB |
| 651 | // | 652 | // |
| 652 | 653 | ||
| 654 | class CSQLiteWalletRuntimeLoadVisitor : public CWalletRecordVisitor | ||
| 655 | { | ||
| 656 | public: | ||
| 657 | bool fHaveStoredMineMode; | ||
| 658 | bool fHaveStoredHDCoinType; | ||
| 659 | string strLastType; | ||
| 660 | |||
| 661 | CSQLiteWalletRuntimeLoadVisitor() : | ||
| 662 | fHaveStoredMineMode(false), | ||
| 663 | fHaveStoredHDCoinType(false) | ||
| 664 | { | ||
| 665 | } | ||
| 666 | |||
| 667 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 668 | const CDataStream& ssValueIn, | ||
| 669 | string& strErrorRet) | ||
| 670 | { | ||
| 671 | CDataStream ssKey = ssKeyIn; | ||
| 672 | CDataStream ssValue = ssValueIn; | ||
| 673 | string strType; | ||
| 674 | ssKey >> strType; | ||
| 675 | strLastType = strType; | ||
| 676 | |||
| 677 | if (strType == "name") | ||
| 678 | { | ||
| 679 | string strAddress; | ||
| 680 | ssKey >> strAddress; | ||
| 681 | ssValue >> mapAddressBook[strAddress]; | ||
| 682 | } | ||
| 683 | else if (strType == "tx") | ||
| 684 | { | ||
| 685 | uint256 hash; | ||
| 686 | ssKey >> hash; | ||
| 687 | CWalletTx& wtx = mapWallet[hash]; | ||
| 688 | ssValue >> wtx; | ||
| 689 | |||
| 690 | if (wtx.GetHash() != hash) | ||
| 691 | printf("Error in SQLite wallet export, hash mismatch\n"); | ||
| 692 | } | ||
| 693 | else if (strType == "key") | ||
| 694 | { | ||
| 695 | vector<unsigned char> vchPubKey; | ||
| 696 | ssKey >> vchPubKey; | ||
| 697 | CPrivKey vchPrivKey; | ||
| 698 | ssValue >> vchPrivKey; | ||
| 699 | |||
| 700 | mapKeys[vchPubKey] = vchPrivKey; | ||
| 701 | mapPubKeys[Hash160(vchPubKey)] = vchPubKey; | ||
| 702 | } | ||
| 703 | else if (strType == "mkey") | ||
| 704 | { | ||
| 705 | unsigned int nID = 0; | ||
| 706 | ssKey >> nID; | ||
| 707 | CWalletMasterKey kMasterKey; | ||
| 708 | ssValue >> kMasterKey; | ||
| 709 | mapMasterKeys[nID] = kMasterKey; | ||
| 710 | nWalletMasterKeyMaxID = max(nWalletMasterKeyMaxID, nID); | ||
| 711 | fWalletEncrypted = true; | ||
| 712 | fWalletLocked = true; | ||
| 713 | } | ||
| 714 | else if (strType == "ckey") | ||
| 715 | { | ||
| 716 | vector<unsigned char> vchPubKey; | ||
| 717 | ssKey >> vchPubKey; | ||
| 718 | vector<unsigned char> vchCryptedSecret; | ||
| 719 | ssValue >> vchCryptedSecret; | ||
| 720 | if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) | ||
| 721 | { | ||
| 722 | strErrorRet = "SQLite wallet export has an unreadable encrypted key record"; | ||
| 723 | return false; | ||
| 724 | } | ||
| 725 | } | ||
| 726 | else if (strType == "defaultkey") | ||
| 727 | { | ||
| 728 | ssValue >> vchDefaultKey; | ||
| 729 | } | ||
| 730 | else if (strType == "hdmaster") | ||
| 731 | { | ||
| 732 | ssValue >> vchHDMaster; | ||
| 733 | } | ||
| 734 | else if (strType == "hdchaincode") | ||
| 735 | { | ||
| 736 | ssValue >> vchHDChainCode; | ||
| 737 | } | ||
| 738 | else if (strType == "hdnext") | ||
| 739 | { | ||
| 740 | ssValue >> nHDNext; | ||
| 741 | } | ||
| 742 | else if (strType == "hdschema") | ||
| 743 | { | ||
| 744 | ssValue >> nHDKeySchema; | ||
| 745 | } | ||
| 746 | else if (strType == "hdcointype") | ||
| 747 | { | ||
| 748 | ssValue >> nHDCoinType; | ||
| 749 | fHaveStoredHDCoinType = true; | ||
| 750 | } | ||
| 751 | else if (strType == "hdreceivenext") | ||
| 752 | { | ||
| 753 | ssValue >> nHDReceiveNext; | ||
| 754 | } | ||
| 755 | else if (strType == "hdchangenext") | ||
| 756 | { | ||
| 757 | ssValue >> nHDChangeNext; | ||
| 758 | } | ||
| 759 | else if (strType == "cryptedhdmaster") | ||
| 760 | { | ||
| 761 | ssValue >> vchCryptedHDMaster; | ||
| 762 | fWalletEncrypted = true; | ||
| 763 | fWalletLocked = true; | ||
| 764 | } | ||
| 765 | else if (strType == "cryptedhdchaincode") | ||
| 766 | { | ||
| 767 | ssValue >> vchCryptedHDChainCode; | ||
| 768 | fWalletEncrypted = true; | ||
| 769 | fWalletLocked = true; | ||
| 770 | } | ||
| 771 | else if (strType == "walletminversion") | ||
| 772 | { | ||
| 773 | int nMinVersion = 0; | ||
| 774 | ssValue >> nMinVersion; | ||
| 775 | if (nMinVersion > WALLET_FORMAT_SUPPORTED) | ||
| 776 | { | ||
| 777 | strWalletLoadError = strprintf( | ||
| 778 | "This SQLite wallet export was written by a newer version of " | ||
| 779 | "Bitflash (wallet format %d; this build understands %d). " | ||
| 780 | "Upgrade Bitflash before opening it.", | ||
| 781 | nMinVersion, WALLET_FORMAT_SUPPORTED); | ||
| 782 | strErrorRet = strWalletLoadError; | ||
| 783 | return false; | ||
| 784 | } | ||
| 785 | } | ||
| 786 | else if (strType == "pool") | ||
| 787 | { | ||
| 788 | int64 nIndex; | ||
| 789 | ssKey >> nIndex; | ||
| 790 | vector<unsigned char> vchPubKey; | ||
| 791 | ssValue >> vchPubKey; | ||
| 792 | mapKeyPool[nIndex] = vchPubKey; | ||
| 793 | } | ||
| 794 | else if (strType == "setting") | ||
| 795 | { | ||
| 796 | string strKey; | ||
| 797 | ssKey >> strKey; | ||
| 798 | if (strKey == "nTransactionFee") ssValue >> nTransactionFee; | ||
| 799 | if (strKey == "addrIncoming") ssValue >> addrIncoming; | ||
| 800 | |||
| 801 | if (!fMineModeFromCommandLine) | ||
| 802 | { | ||
| 803 | if (strKey == "nMineMode") { ssValue >> nMineMode; fHaveStoredMineMode = true; } | ||
| 804 | if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins; | ||
| 805 | if (strKey == "strParticipantPool") ssValue >> strParticipantPool; | ||
| 806 | } | ||
| 807 | } | ||
| 808 | |||
| 809 | if (ssKey.fail()) | ||
| 810 | { | ||
| 811 | strErrorRet = strprintf("malformed SQLite wallet record key while handling '%s'", | ||
| 812 | strLastType.c_str()); | ||
| 813 | return false; | ||
| 814 | } | ||
| 815 | if (ssValue.fail()) | ||
| 816 | { | ||
| 817 | strErrorRet = strprintf("malformed SQLite wallet record value while handling '%s'", | ||
| 818 | strLastType.c_str()); | ||
| 819 | return false; | ||
| 820 | } | ||
| 821 | return true; | ||
| 822 | } | ||
| 823 | |||
| 824 | vector<unsigned char> vchDefaultKey; | ||
| 825 | }; | ||
| 826 | |||
| 827 | static bool FinishLoadedWallet(const vector<unsigned char>& vchDefaultKey, | ||
| 828 | bool fMayCreateDefaultKey) | ||
| 829 | { | ||
| 830 | if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey))) | ||
| 831 | { | ||
| 832 | keyUser.SetPubKey(vchDefaultKey); | ||
| 833 | if (mapKeys.count(vchDefaultKey)) | ||
| 834 | keyUser.SetPrivKey(mapKeys[vchDefaultKey]); | ||
| 835 | } | ||
| 836 | else if (IsWalletEncrypted()) | ||
| 837 | { | ||
| 838 | printf("LoadWallet: encrypted wallet has no usable default public key. " | ||
| 839 | "The file was left untouched.\n"); | ||
| 840 | return false; | ||
| 841 | } | ||
| 842 | else | ||
| 843 | { | ||
| 844 | if (!fMayCreateDefaultKey) | ||
| 845 | { | ||
| 846 | strWalletLoadError = | ||
| 847 | "SQLite wallet export has no usable default public key"; | ||
| 848 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 849 | return false; | ||
| 850 | } | ||
| 851 | |||
| 852 | keyUser.MakeNewKey(); | ||
| 853 | if (!AddKey(keyUser)) | ||
| 854 | return false; | ||
| 855 | if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) | ||
| 856 | return false; | ||
| 857 | CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); | ||
| 858 | } | ||
| 859 | |||
| 860 | return true; | ||
| 861 | } | ||
| 862 | |||
| 863 | static bool ReconcileLoadedWalletMetadata(bool fHaveStoredMineMode, | ||
| 864 | bool fHaveStoredHDCoinType) | ||
| 865 | { | ||
| 866 | // An encrypted wallet has a seed; it just has not been decrypted yet. | ||
| 867 | // | ||
| 868 | // The reset in the else branch was written when "no seed in memory after | ||
| 869 | // loading" could only mean "this wallet has no seed". With encryption that | ||
| 870 | // is no longer true, and taking the else branch here wiped the schema and | ||
| 871 | // the derivation counters that had just been read correctly out of the | ||
| 872 | // file. Unlocking later restores the seed but nothing restores those, so a | ||
| 873 | // BIP44 wallet came back deriving legacy addresses -- different addresses | ||
| 874 | // from the ones its own recovery phrase produces, while the audit reported | ||
| 875 | // the phrase as covering nothing. | ||
| 876 | if (HaveHDSeed() || !vchCryptedHDMaster.empty()) | ||
| 877 | { | ||
| 878 | if (nHDKeySchema == HD_SCHEMA_NONE) | ||
| 879 | { | ||
| 880 | nHDKeySchema = HD_SCHEMA_LEGACY; | ||
| 881 | printf("LoadWallet: deterministic seed has no schema metadata; " | ||
| 882 | "treating it as legacy m/index'\n"); | ||
| 883 | } | ||
| 884 | else if (nHDKeySchema != HD_SCHEMA_LEGACY && | ||
| 885 | nHDKeySchema != HD_SCHEMA_BIP44) | ||
| 886 | { | ||
| 887 | printf("LoadWallet: deterministic wallet schema %d (%s) is not " | ||
| 888 | "supported by this build\n", | ||
| 889 | nHDKeySchema, HDKeySchemaName(nHDKeySchema).c_str()); | ||
| 890 | return false; | ||
| 891 | } | ||
| 892 | if (!fHaveStoredHDCoinType) | ||
| 893 | { | ||
| 894 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; | ||
| 895 | printf("LoadWallet: deterministic seed has no BIP44 coin type metadata; " | ||
| 896 | "using Bitflash coin type %u\n", nHDCoinType); | ||
| 897 | } | ||
| 898 | } | ||
| 899 | else | ||
| 900 | { | ||
| 901 | nHDKeySchema = HD_SCHEMA_NONE; | ||
| 902 | nHDNext = 0; | ||
| 903 | nHDReceiveNext = 0; | ||
| 904 | nHDChangeNext = 0; | ||
| 905 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; | ||
| 906 | } | ||
| 907 | |||
| 908 | // fGenerateBitcoins and nMineMode only mean anything together, and a | ||
| 909 | // wallet.dat can easily hold one without the other: Bitcoin 0.1.0 already | ||
| 910 | // wrote fGenerateBitcoins when mining was toggled, years before this fork | ||
| 911 | // had a mode at all. Restoring that lone flag put a node into the one state | ||
| 912 | // the miner cannot act on -- generate on, mode relay -- so every miner | ||
| 913 | // thread started and returned at the relay guard, and the machine sat there | ||
| 914 | // looking like it was mining while hashing nothing. That happened to a | ||
| 915 | // 32-core node on the first 1.2.11 start. | ||
| 916 | // | ||
| 917 | // So the flag is only believed when the mode it belongs to was stored with | ||
| 918 | // it, and the pair is made coherent either way. | ||
| 919 | if (!fMineModeFromCommandLine) | ||
| 920 | { | ||
| 921 | if (!fHaveStoredMineMode) | ||
| 922 | { | ||
| 923 | if (fGenerateBitcoins) | ||
| 924 | printf("LoadWallet: ignoring a stored fGenerateBitcoins with no mining mode " | ||
| 925 | "beside it -- it predates this setting and cannot be read on its own\n"); | ||
| 926 | fGenerateBitcoins = 0; | ||
| 927 | } | ||
| 928 | else | ||
| 929 | { | ||
| 930 | fGenerateBitcoins = (nMineMode != MINE_RELAY) ? 1 : 0; | ||
| 931 | } | ||
| 932 | } | ||
| 933 | |||
| 934 | return true; | ||
| 935 | } | ||
| 936 | |||
| 653 | bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | 937 | bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) |
| 654 | { | 938 | { |
| 655 | // Whether wallet.dat actually carried a mining mode, as opposed to just the | 939 | // Whether wallet.dat actually carried a mining mode, as opposed to just the |
| @@ -1005,31 +1289,74 @@ bool LoadWallet() | |||
| 1005 | if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) | 1289 | if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) |
| 1006 | return false; | 1290 | return false; |
| 1007 | 1291 | ||
| 1008 | if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey))) | 1292 | return FinishLoadedWallet(vchDefaultKey, true); |
| 1293 | } | ||
| 1294 | |||
| 1295 | bool LoadWalletFromSQLite(const string& strPath) | ||
| 1296 | { | ||
| 1297 | strWalletLoadError.clear(); | ||
| 1298 | |||
| 1299 | if (!FileExists(strPath.c_str())) | ||
| 1009 | { | 1300 | { |
| 1010 | // Set keyUser | 1301 | strWalletLoadError = strprintf("SQLite wallet export does not exist: %s", |
| 1011 | keyUser.SetPubKey(vchDefaultKey); | 1302 | strPath.c_str()); |
| 1012 | if (mapKeys.count(vchDefaultKey)) | 1303 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); |
| 1013 | keyUser.SetPrivKey(mapKeys[vchDefaultKey]); | 1304 | return false; |
| 1014 | } | 1305 | } |
| 1015 | else if (IsWalletEncrypted()) | 1306 | |
| 1307 | CWalletDBSQLite sqlite; | ||
| 1308 | string strError; | ||
| 1309 | if (!sqlite.OpenReadOnly(strPath, strError)) | ||
| 1016 | { | 1310 | { |
| 1017 | printf("LoadWallet: encrypted wallet has no usable default public key. " | 1311 | strWalletLoadError = strError; |
| 1018 | "The file was left untouched.\n"); | 1312 | printf("LoadWallet: cannot open SQLite wallet export: %s\n", |
| 1313 | strError.c_str()); | ||
| 1019 | return false; | 1314 | return false; |
| 1020 | } | 1315 | } |
| 1021 | else | 1316 | |
| 1317 | CSQLiteWalletRuntimeLoadVisitor visitor; | ||
| 1318 | try | ||
| 1022 | { | 1319 | { |
| 1023 | // Create new keyUser and set as default key | 1320 | CRITICAL_BLOCK(cs_mapKeys) |
| 1024 | keyUser.MakeNewKey(); | 1321 | CRITICAL_BLOCK(cs_mapWallet) |
| 1025 | if (!AddKey(keyUser)) | 1322 | { |
| 1026 | return false; | 1323 | if (!sqlite.ScanRecords(visitor, strError)) |
| 1027 | if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) | 1324 | { |
| 1028 | return false; | 1325 | if (strWalletLoadError.empty()) |
| 1029 | CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); | 1326 | strWalletLoadError = strError; |
| 1327 | printf("LoadWallet: SQLite wallet export could not be read. " | ||
| 1328 | "Failed while handling a '%s' record: %s\n", | ||
| 1329 | visitor.strLastType.c_str(), strError.c_str()); | ||
| 1330 | return false; | ||
| 1331 | } | ||
| 1332 | } | ||
| 1333 | } | ||
| 1334 | catch (const std::exception& e) | ||
| 1335 | { | ||
| 1336 | strWalletLoadError = strprintf( | ||
| 1337 | "SQLite wallet export could not be read while handling a '%s' record: %s", | ||
| 1338 | visitor.strLastType.c_str(), e.what()); | ||
| 1339 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1340 | return false; | ||
| 1341 | } | ||
| 1342 | catch (...) | ||
| 1343 | { | ||
| 1344 | strWalletLoadError = strprintf( | ||
| 1345 | "SQLite wallet export could not be read while handling a '%s' record", | ||
| 1346 | visitor.strLastType.c_str()); | ||
| 1347 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1348 | return false; | ||
| 1030 | } | 1349 | } |
| 1031 | 1350 | ||
| 1032 | return true; | 1351 | if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode, |
| 1352 | visitor.fHaveStoredHDCoinType)) | ||
| 1353 | { | ||
| 1354 | if (strWalletLoadError.empty()) | ||
| 1355 | strWalletLoadError = "SQLite wallet metadata is not supported by this build"; | ||
| 1356 | return false; | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | return FinishLoadedWallet(visitor.vchDefaultKey, false); | ||
| 1033 | } | 1360 | } |
| 1034 | 1361 | ||
| 1035 | 1362 | ||