diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 18:17:23 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 18:17:23 -0300 |
| commit | f9ee957bd6d3f28dfee166a82f389aad666a0119 (patch) | |
| tree | e6ecab8a276afb007f341a498dd1a8553a756b0c /src | |
| parent | c56af18e421dff78446399a630b5fa949fc487b3 (diff) | |
wallet: stage SQLite runtime loading
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 361 | ||||
| -rw-r--r-- | src/db.h | 1 | ||||
| -rw-r--r-- | src/main_gui.cpp | 22 | ||||
| -rw-r--r-- | src/selftest.cpp | 29 | ||||
| -rw-r--r-- | src/wallet_sqlite.cpp | 19 | ||||
| -rw-r--r-- | src/wallet_sqlite.h | 1 |
6 files changed, 416 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 | ||
| @@ -536,6 +536,7 @@ public: | |||
| 536 | bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet); | 536 | bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet); |
| 537 | 537 | ||
| 538 | bool LoadWallet(); | 538 | bool LoadWallet(); |
| 539 | bool LoadWalletFromSQLite(const string& strPath); | ||
| 539 | 540 | ||
| 540 | // Writes a wallet.dat that opens on its own, anywhere -- see the definition in | 541 | // Writes a wallet.dat that opens on its own, anywhere -- see the definition in |
| 541 | // db.cpp for why a plain file copy does not. Still a point-in-time snapshot: | 542 | // db.cpp for why a plain file copy does not. Still a point-in-time snapshot: |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index c9aac7d..ee875c7 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -189,6 +189,7 @@ static void PrintUsage() | |||
| 189 | printf(" an empty data directory, then exit)\n"); | 189 | printf(" an empty data directory, then exit)\n"); |
| 190 | printf(" /walletsqliteloadcheck=FILE\n"); | 190 | printf(" /walletsqliteloadcheck=FILE\n"); |
| 191 | printf(" (parse a SQLite export like the wallet loader)\n"); | 191 | printf(" (parse a SQLite export like the wallet loader)\n"); |
| 192 | printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n"); | ||
| 192 | printf(" /rescan (walk the chain for coins this wallet owns\n"); | 193 | printf(" /rescan (walk the chain for coins this wallet owns\n"); |
| 193 | printf(" but never recorded, then exit)\n"); | 194 | printf(" but never recorded, then exit)\n"); |
| 194 | printf("\n"); | 195 | printf("\n"); |
| @@ -617,6 +618,27 @@ int main(int argc, char* argv[]) | |||
| 617 | printf("Loading wallet...\n"); | 618 | printf("Loading wallet...\n"); |
| 618 | try | 619 | try |
| 619 | { | 620 | { |
| 621 | string strWalletSQLite = | ||
| 622 | argval2(argc, argv, "/walletsqlite", "-walletsqlite"); | ||
| 623 | if (!strWalletSQLite.empty()) | ||
| 624 | { | ||
| 625 | AttachTerminal(); | ||
| 626 | if (!LoadWalletFromSQLite(strWalletSQLite)) | ||
| 627 | { | ||
| 628 | FatalStartupError(fHeadlessStartup, | ||
| 629 | "Cannot open SQLite wallet export.", | ||
| 630 | strWalletLoadError); | ||
| 631 | return 1; | ||
| 632 | } | ||
| 633 | |||
| 634 | fprintf(stderr, "SQLite wallet loaded: read-only staging backend\n"); | ||
| 635 | fprintf(stderr, "SQLite runtime writes are not enabled yet; exiting before " | ||
| 636 | "network, mining, GUI, or wallet mutation starts.\n"); | ||
| 637 | fflush(stderr); | ||
| 638 | DBFlush(true); | ||
| 639 | return 0; | ||
| 640 | } | ||
| 641 | |||
| 620 | if (!LoadWallet()) | 642 | if (!LoadWallet()) |
| 621 | { | 643 | { |
| 622 | // LoadWallet() explains itself into strWalletLoadError when it | 644 | // LoadWallet() explains itself into strWalletLoadError when it |
diff --git a/src/selftest.cpp b/src/selftest.cpp index feb4076..14bf45a 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -1298,6 +1298,19 @@ static int RunWalletSQLiteMigrationSelfTest() | |||
| 1298 | "load check: ok"), | 1298 | "load check: ok"), |
| 1299 | "SQLite wallet export passes the loader compatibility check") ? 0 : 1; | 1299 | "SQLite wallet export passes the loader compatibility check") ? 0 : 1; |
| 1300 | 1300 | ||
| 1301 | string strRuntimeOut = tmp + "/sqlite-runtime.txt"; | ||
| 1302 | vector<string> vRuntimeArgs; | ||
| 1303 | vRuntimeArgs.push_back("-datadir=" + strWalletDir); | ||
| 1304 | vRuntimeArgs.push_back("-nomanagedtor"); | ||
| 1305 | vRuntimeArgs.push_back("-nogui"); | ||
| 1306 | vRuntimeArgs.push_back("-walletsqlite=" + strSQLite); | ||
| 1307 | int nRuntimeRet = | ||
| 1308 | RunBitflashChild(strExe, vRuntimeArgs, NULL, &strRuntimeOut); | ||
| 1309 | nFail += Check(nRuntimeRet == 0 && | ||
| 1310 | FileContainsText(strRuntimeOut, | ||
| 1311 | "SQLite wallet loaded: read-only staging backend"), | ||
| 1312 | "SQLite wallet export loads through the runtime startup path") ? 0 : 1; | ||
| 1313 | |||
| 1301 | string strRestoredDir = tmp + "/restored-wallet"; | 1314 | string strRestoredDir = tmp + "/restored-wallet"; |
| 1302 | nFail += Check(MakeDirLocal(strRestoredDir), | 1315 | nFail += Check(MakeDirLocal(strRestoredDir), |
| 1303 | "restored wallet directory can be created") ? 0 : 1; | 1316 | "restored wallet directory can be created") ? 0 : 1; |
| @@ -1372,6 +1385,22 @@ static int RunWalletSQLiteMigrationSelfTest() | |||
| 1372 | "load check: failed"), | 1385 | "load check: failed"), |
| 1373 | "SQLite wallet load check rejects future wallet formats") ? 0 : 1; | 1386 | "SQLite wallet load check rejects future wallet formats") ? 0 : 1; |
| 1374 | 1387 | ||
| 1388 | string strFutureRuntimeOut = tmp + "/sqlite-runtime-future.txt"; | ||
| 1389 | vector<string> vFutureRuntimeArgs; | ||
| 1390 | vFutureRuntimeArgs.push_back("-datadir=" + strWalletDir); | ||
| 1391 | vFutureRuntimeArgs.push_back("-nomanagedtor"); | ||
| 1392 | vFutureRuntimeArgs.push_back("-nogui"); | ||
| 1393 | vFutureRuntimeArgs.push_back("-walletsqlite=" + strFutureSQLite); | ||
| 1394 | int nFutureRuntimeRet = | ||
| 1395 | RunBitflashChild(strExe, vFutureRuntimeArgs, NULL, | ||
| 1396 | &strFutureRuntimeOut); | ||
| 1397 | nFail += Check(nFutureRuntimeRet == 1 && | ||
| 1398 | FileContainsText(strFutureRuntimeOut, | ||
| 1399 | "Cannot open SQLite wallet export") && | ||
| 1400 | FileContainsText(strFutureRuntimeOut, | ||
| 1401 | "newer version of Bitflash"), | ||
| 1402 | "SQLite runtime startup rejects future wallet formats") ? 0 : 1; | ||
| 1403 | |||
| 1375 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; | 1404 | string strOverwriteOut = tmp + "/sqlite-export-overwrite.txt"; |
| 1376 | int nOverwriteRet = | 1405 | int nOverwriteRet = |
| 1377 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); | 1406 | RunBitflashChild(strExe, vExportArgs, NULL, &strOverwriteOut); |
diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp index 8489cac..ca81573 100644 --- a/src/wallet_sqlite.cpp +++ b/src/wallet_sqlite.cpp | |||
| @@ -58,6 +58,25 @@ bool CWalletDBSQLite::Open(const string& strPath, string& strError) | |||
| 58 | return true; | 58 | return true; |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | bool CWalletDBSQLite::OpenReadOnly(const string& strPath, string& strError) | ||
| 62 | { | ||
| 63 | Close(); | ||
| 64 | |||
| 65 | int ret = sqlite3_open_v2(strPath.c_str(), &pdb, | ||
| 66 | SQLITE_OPEN_READONLY | | ||
| 67 | SQLITE_OPEN_FULLMUTEX, | ||
| 68 | NULL); | ||
| 69 | if (ret != SQLITE_OK) | ||
| 70 | { | ||
| 71 | SetSQLiteError(pdb, "cannot open SQLite wallet read-only", strError); | ||
| 72 | Close(); | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | sqlite3_busy_timeout(pdb, 5000); | ||
| 77 | return true; | ||
| 78 | } | ||
| 79 | |||
| 61 | void CWalletDBSQLite::Close() | 80 | void CWalletDBSQLite::Close() |
| 62 | { | 81 | { |
| 63 | if (pdb) | 82 | if (pdb) |
diff --git a/src/wallet_sqlite.h b/src/wallet_sqlite.h index 385a340..5e6467b 100644 --- a/src/wallet_sqlite.h +++ b/src/wallet_sqlite.h | |||
| @@ -15,6 +15,7 @@ public: | |||
| 15 | ~CWalletDBSQLite(); | 15 | ~CWalletDBSQLite(); |
| 16 | 16 | ||
| 17 | bool Open(const string& strPath, string& strError); | 17 | bool Open(const string& strPath, string& strError); |
| 18 | bool OpenReadOnly(const string& strPath, string& strError); | ||
| 18 | void Close(); | 19 | void Close(); |
| 19 | 20 | ||
| 20 | bool BeginTransaction(string& strError); | 21 | bool BeginTransaction(string& strError); |