diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 15:03:21 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-14 15:20:54 -0300 |
| commit | c49b92a9a79a398b51200e69f7dde8a467b9c925 (patch) | |
| tree | 25c86213839210a08ff69983e7219e924d5dbb85 | |
| parent | 9b281d890168c7c912857530d787fbc82203d848 (diff) | |
wallet: add experimental SQLite record store
| -rw-r--r-- | .github/workflows/ci.yml | 1 | ||||
| -rw-r--r-- | Makefile | 8 | ||||
| -rw-r--r-- | docs/storage.md | 19 | ||||
| -rw-r--r-- | src/Makefile | 10 | ||||
| -rw-r--r-- | src/main_gui.cpp | 3 | ||||
| -rw-r--r-- | src/makefile.mingw | 8 | ||||
| -rw-r--r-- | src/selftest.cpp | 108 | ||||
| -rw-r--r-- | src/wallet_sqlite.cpp | 254 | ||||
| -rw-r--r-- | src/wallet_sqlite.h | 40 |
9 files changed, 439 insertions, 12 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4045f4..e22b5a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml | |||
| @@ -56,6 +56,7 @@ jobs: | |||
| 56 | mingw-w64-ucrt-x86_64-glfw | 56 | mingw-w64-ucrt-x86_64-glfw |
| 57 | mingw-w64-ucrt-x86_64-openssl | 57 | mingw-w64-ucrt-x86_64-openssl |
| 58 | mingw-w64-ucrt-x86_64-db | 58 | mingw-w64-ucrt-x86_64-db |
| 59 | mingw-w64-ucrt-x86_64-sqlite3 | ||
| 59 | mingw-w64-ucrt-x86_64-libsodium | 60 | mingw-w64-ucrt-x86_64-libsodium |
| 60 | mingw-w64-ucrt-x86_64-nlohmann-json | 61 | mingw-w64-ucrt-x86_64-nlohmann-json |
| 61 | mingw-w64-ucrt-x86_64-boost | 62 | mingw-w64-ucrt-x86_64-boost |
| @@ -43,10 +43,11 @@ deps-apt: | |||
| 43 | @if command -v apt-get >/dev/null 2>&1; then \ | 43 | @if command -v apt-get >/dev/null 2>&1; then \ |
| 44 | $(SUDO) apt-get install -y build-essential cmake git pkg-config autoconf \ | 44 | $(SUDO) apt-get install -y build-essential cmake git pkg-config autoconf \ |
| 45 | libtool libssl-dev libdb5.3++-dev libsodium-dev nlohmann-json3-dev \ | 45 | libtool libssl-dev libdb5.3++-dev libsodium-dev nlohmann-json3-dev \ |
| 46 | libboost-system-dev libglfw3-dev libgl-dev python3-pil clang; \ | 46 | libboost-system-dev libglfw3-dev libgl-dev libsqlite3-dev \ |
| 47 | python3-pil clang; \ | ||
| 47 | else \ | 48 | else \ |
| 48 | echo "Not an apt system — install: g++ cmake git autoconf libtool"; \ | 49 | echo "Not an apt system — install: g++ cmake git autoconf libtool"; \ |
| 49 | echo " libssl libdb++ libsodium nlohmann-json boost glfw3 opengl python3-pil"; \ | 50 | echo " libssl libdb++ libsodium nlohmann-json boost glfw3 opengl sqlite3 python3-pil"; \ |
| 50 | fi | 51 | fi |
| 51 | 52 | ||
| 52 | deps-secp256k1: | 53 | deps-secp256k1: |
| @@ -113,7 +114,7 @@ img = ico.convert('RGBA'); \ | |||
| 113 | img.save('/tmp/bitflash.AppDir/bitflash.png'); \ | 114 | img.save('/tmp/bitflash.AppDir/bitflash.png'); \ |
| 114 | import shutil; shutil.copy('/tmp/bitflash.AppDir/bitflash.png', \ | 115 | import shutil; shutil.copy('/tmp/bitflash.AppDir/bitflash.png', \ |
| 115 | '/tmp/bitflash.AppDir/usr/share/icons/hicolor/256x256/apps/bitflash.png')" | 116 | '/tmp/bitflash.AppDir/usr/share/icons/hicolor/256x256/apps/bitflash.png')" |
| 116 | @for lib in libsodium.so.23 libdb_cxx-5.3.so libglfw.so.3; do \ | 117 | @for lib in libsodium.so.23 libdb_cxx-5.3.so libsqlite3.so.0 libglfw.so.3; do \ |
| 117 | path=$$(ldconfig -p | grep " $$lib " | awk '{print $$NF}' | head -1); \ | 118 | path=$$(ldconfig -p | grep " $$lib " | awk '{print $$NF}' | head -1); \ |
| 118 | [ -z "$$path" ] && path=$$(find /usr/lib /lib -name "$$lib" 2>/dev/null | head -1); \ | 119 | [ -z "$$path" ] && path=$$(find /usr/lib /lib -name "$$lib" 2>/dev/null | head -1); \ |
| 119 | [ -n "$$path" ] && cp -L "$$path" /tmp/bitflash.AppDir/usr/lib/ && echo " bundled $$lib" || echo " missing $$lib"; \ | 120 | [ -n "$$path" ] && cp -L "$$path" /tmp/bitflash.AppDir/usr/lib/ && echo " bundled $$lib" || echo " missing $$lib"; \ |
| @@ -131,6 +132,7 @@ deps-windows: | |||
| 131 | mingw-w64-ucrt-x86_64-glfw \ | 132 | mingw-w64-ucrt-x86_64-glfw \ |
| 132 | mingw-w64-ucrt-x86_64-openssl \ | 133 | mingw-w64-ucrt-x86_64-openssl \ |
| 133 | mingw-w64-ucrt-x86_64-db \ | 134 | mingw-w64-ucrt-x86_64-db \ |
| 135 | mingw-w64-ucrt-x86_64-sqlite3 \ | ||
| 134 | mingw-w64-ucrt-x86_64-libsodium \ | 136 | mingw-w64-ucrt-x86_64-libsodium \ |
| 135 | mingw-w64-ucrt-x86_64-nlohmann-json \ | 137 | mingw-w64-ucrt-x86_64-nlohmann-json \ |
| 136 | mingw-w64-ucrt-x86_64-boost \ | 138 | mingw-w64-ucrt-x86_64-boost \ |
diff --git a/docs/storage.md b/docs/storage.md index c1cda45..f75f9b9 100644 --- a/docs/storage.md +++ b/docs/storage.md | |||
| @@ -85,6 +85,25 @@ A future wallet backend must keep these properties: | |||
| 85 | - recovery phrase encrypted and locked, requiring unlock before coverage can | 85 | - recovery phrase encrypted and locked, requiring unlock before coverage can |
| 86 | be audited. | 86 | be audited. |
| 87 | 87 | ||
| 88 | ## Experimental SQLite Wallet Store | ||
| 89 | |||
| 90 | The SQLite wallet path starts as a byte-preserving record store, not as a new | ||
| 91 | wallet format: | ||
| 92 | |||
| 93 | ```sql | ||
| 94 | CREATE TABLE wallet_records ( | ||
| 95 | key BLOB PRIMARY KEY NOT NULL, | ||
| 96 | value BLOB NOT NULL | ||
| 97 | ); | ||
| 98 | PRAGMA user_version=1; | ||
| 99 | ``` | ||
| 100 | |||
| 101 | Keys and values are the same serialized `CDataStream` bytes used by `wallet.dat`. | ||
| 102 | The first implementation is exercised only by `-selftest=wallet-sqlite`; the | ||
| 103 | runtime wallet still opens Berkeley DB. The purpose is to prove schema creation, | ||
| 104 | raw record write/read, close/reopen behavior, and value preservation before any | ||
| 105 | BDB-to-SQLite migration or runtime backend flag exists. | ||
| 106 | |||
| 88 | ## `blkindex.dat` | 107 | ## `blkindex.dat` |
| 89 | 108 | ||
| 90 | `blkindex.dat` is accessed through `CTxDB`. | 109 | `blkindex.dat` is accessed through `CTxDB`. |
diff --git a/src/Makefile b/src/Makefile index 19ad33a..cfa40f6 100644 --- a/src/Makefile +++ b/src/Makefile | |||
| @@ -40,7 +40,7 @@ FUZZ_LDFLAGS = -fsanitize=fuzzer,address,undefined | |||
| 40 | 40 | ||
| 41 | LIBS = -L$(SECP_LIB) -lsecp256k1 \ | 41 | LIBS = -L$(SECP_LIB) -lsecp256k1 \ |
| 42 | -L$(RANDOMX_DIR)/build -lrandomx \ | 42 | -L$(RANDOMX_DIR)/build -lrandomx \ |
| 43 | -lsodium -lssl -lcrypto -ldb_cxx -lpthread \ | 43 | -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread \ |
| 44 | -lglfw -lGL | 44 | -lglfw -lGL |
| 45 | 45 | ||
| 46 | # Every header a .cpp here includes has to be listed, not just the interesting | 46 | # Every header a .cpp here includes has to be listed, not just the interesting |
| @@ -53,7 +53,7 @@ LIBS = -L$(SECP_LIB) -lsecp256k1 \ | |||
| 53 | HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ | 53 | HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ |
| 54 | key.h bignum.h script.h db.h base58.h net.h nostr.h \ | 54 | key.h bignum.h script.h db.h base58.h net.h nostr.h \ |
| 55 | randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h market.h \ | 55 | randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h market.h \ |
| 56 | selftest.h walletcmd.h compat.h sockcount.h proxy.h tor.h font_roboto.h | 56 | selftest.h walletcmd.h wallet_sqlite.h compat.h sockcount.h proxy.h tor.h font_roboto.h |
| 57 | 57 | ||
| 58 | IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \ | 58 | IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \ |
| 59 | obj/imgui_widgets.o obj/imgui_impl_glfw.o obj/imgui_impl_opengl3.o | 59 | obj/imgui_widgets.o obj/imgui_impl_glfw.o obj/imgui_impl_opengl3.o |
| @@ -61,7 +61,8 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \ | |||
| 61 | NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ | 61 | NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ |
| 62 | obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ | 62 | obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ |
| 63 | obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ | 63 | obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ |
| 64 | obj/selftest.o obj/bip32.o obj/walletcmd.o obj/proxy.o obj/tor.o | 64 | obj/selftest.o obj/bip32.o obj/walletcmd.o obj/wallet_sqlite.o \ |
| 65 | obj/proxy.o obj/tor.o | ||
| 65 | 66 | ||
| 66 | GUI_OBJS = obj/gui.o obj/main_gui.o | 67 | GUI_OBJS = obj/gui.o obj/main_gui.o |
| 67 | 68 | ||
| @@ -116,7 +117,7 @@ bitflash: $(IMGUI_OBJS) $(NODE_OBJS) $(GUI_OBJS) | |||
| 116 | 117 | ||
| 117 | NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \ | 118 | NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \ |
| 118 | -L$(RANDOMX_DIR)/build -lrandomx \ | 119 | -L$(RANDOMX_DIR)/build -lrandomx \ |
| 119 | -lsodium -lssl -lcrypto -ldb_cxx -lpthread | 120 | -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread |
| 120 | 121 | ||
| 121 | NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \ | 122 | NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \ |
| 122 | obj-node/main_gui.o obj-node/headless.o | 123 | obj-node/main_gui.o obj-node/headless.o |
| @@ -187,6 +188,7 @@ tests: test_bip32 bitflash-node | |||
| 187 | ./bitflash-node -selftest=wallet-keypool | 188 | ./bitflash-node -selftest=wallet-keypool |
| 188 | ./bitflash-node -selftest=wallet-hd | 189 | ./bitflash-node -selftest=wallet-hd |
| 189 | ./bitflash-node -selftest=wallet-format | 190 | ./bitflash-node -selftest=wallet-format |
| 191 | ./bitflash-node -selftest=wallet-sqlite | ||
| 190 | ./bitflash-node -selftest=wallet-crypto | 192 | ./bitflash-node -selftest=wallet-crypto |
| 191 | ./bitflash-node -selftest=wallet-encrypt | 193 | ./bitflash-node -selftest=wallet-encrypt |
| 192 | ./bitflash-node -selftest=net-message | 194 | ./bitflash-node -selftest=net-message |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 705b4ef..965c818 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -114,7 +114,8 @@ static void PrintUsage() | |||
| 114 | printf(" /gen\n"); | 114 | printf(" /gen\n"); |
| 115 | printf(" /nogui or /daemon\n"); | 115 | printf(" /nogui or /daemon\n"); |
| 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n"); | 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n"); |
| 117 | printf(" db-env-reopen, wallet-crypto, wallet-encrypt, wallet-portability,\n"); | 117 | printf(" db-env-reopen, wallet-sqlite, wallet-crypto, wallet-encrypt,\n"); |
| 118 | printf(" wallet-portability,\n"); | ||
| 118 | printf(" net-message,\n"); | 119 | printf(" net-message,\n"); |
| 119 | printf(" consensus-limits, pool-stratum,\n"); | 120 | printf(" consensus-limits, pool-stratum,\n"); |
| 120 | printf(" parse-money, socks5-proxy, or managed-tor\n"); | 121 | printf(" parse-money, socks5-proxy, or managed-tor\n"); |
diff --git a/src/makefile.mingw b/src/makefile.mingw index 333be96..2ff4e11 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw | |||
| @@ -24,7 +24,7 @@ CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \ | |||
| 24 | 24 | ||
| 25 | LIBS = -L$(DEPS)/lib -lsecp256k1 \ | 25 | LIBS = -L$(DEPS)/lib -lsecp256k1 \ |
| 26 | -L$(RANDOMX)/build -lrandomx \ | 26 | -L$(RANDOMX)/build -lrandomx \ |
| 27 | -lsodium -lssl -lcrypto -ldb_cxx -lpthread \ | 27 | -lsodium -lssl -lcrypto -ldb_cxx -lsqlite3 -lpthread \ |
| 28 | -LC:/msys64/ucrt64/lib \ | 28 | -LC:/msys64/ucrt64/lib \ |
| 29 | -lglfw3 -lopengl32 -lgdi32 \ | 29 | -lglfw3 -lopengl32 -lgdi32 \ |
| 30 | -lkernel32 -luser32 -lcomdlg32 -lwinspool -lwinmm \ | 30 | -lkernel32 -luser32 -lcomdlg32 -lwinspool -lwinmm \ |
| @@ -37,7 +37,8 @@ IMGUI_OBJS = obj/imgui.o obj/imgui_draw.o obj/imgui_tables.o \ | |||
| 37 | NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ | 37 | NODE_OBJS = obj/util.o obj/script.o obj/db.o obj/net.o obj/main.o \ |
| 38 | obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ | 38 | obj/market.o obj/sha.o obj/nostr.o obj/randomx_pow.o \ |
| 39 | obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ | 39 | obj/btfaddr.o obj/btfchan.o obj/btfrv.o obj/btftunnel.o obj/rpc.o \ |
| 40 | obj/selftest.o obj/bip32.o obj/walletcmd.o obj/proxy.o obj/tor.o | 40 | obj/selftest.o obj/bip32.o obj/walletcmd.o obj/wallet_sqlite.o \ |
| 41 | obj/proxy.o obj/tor.o | ||
| 41 | 42 | ||
| 42 | GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o | 43 | GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o |
| 43 | 44 | ||
| @@ -51,7 +52,7 @@ GUI_OBJS = obj/gui.o obj/main_gui.o obj/bitflash_res.o | |||
| 51 | HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ | 52 | HEADERS = headers_core.h headers.h util.h main.h serialize.h uint256.h \ |
| 52 | key.h bignum.h script.h db.h base58.h net.h nostr.h \ | 53 | key.h bignum.h script.h db.h base58.h net.h nostr.h \ |
| 53 | randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h market.h \ | 54 | randomx_pow.h btfaddr.h btfchan.h btfrv.h btftunnel.h market.h \ |
| 54 | selftest.h walletcmd.h sockcount.h proxy.h tor.h compat.h font_roboto.h | 55 | selftest.h walletcmd.h wallet_sqlite.h sockcount.h proxy.h tor.h compat.h font_roboto.h |
| 55 | 56 | ||
| 56 | all: bitflash.exe | 57 | all: bitflash.exe |
| 57 | 58 | ||
| @@ -107,6 +108,7 @@ tests: test_bip32.exe bitflash.exe | |||
| 107 | ./bitflash.exe -selftest=wallet-keypool | 108 | ./bitflash.exe -selftest=wallet-keypool |
| 108 | ./bitflash.exe -selftest=wallet-hd | 109 | ./bitflash.exe -selftest=wallet-hd |
| 109 | ./bitflash.exe -selftest=wallet-format | 110 | ./bitflash.exe -selftest=wallet-format |
| 111 | ./bitflash.exe -selftest=wallet-sqlite | ||
| 110 | ./bitflash.exe -selftest=wallet-crypto | 112 | ./bitflash.exe -selftest=wallet-crypto |
| 111 | ./bitflash.exe -selftest=wallet-encrypt | 113 | ./bitflash.exe -selftest=wallet-encrypt |
| 112 | ./bitflash.exe -selftest=net-message | 114 | ./bitflash.exe -selftest=net-message |
diff --git a/src/selftest.cpp b/src/selftest.cpp index 31c168f..f9c4a8e 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "sockcount.h" | 13 | #include "sockcount.h" |
| 14 | #include "tor.h" | 14 | #include "tor.h" |
| 15 | #include "walletcmd.h" | 15 | #include "walletcmd.h" |
| 16 | #include "wallet_sqlite.h" | ||
| 16 | 17 | ||
| 17 | extern int RunPoolStratumSelfTest(); | 18 | extern int RunPoolStratumSelfTest(); |
| 18 | 19 | ||
| @@ -62,6 +63,11 @@ static std::string HexStrLocal(const std::vector<unsigned char>& v) | |||
| 62 | return out; | 63 | return out; |
| 63 | } | 64 | } |
| 64 | 65 | ||
| 66 | static vector<unsigned char> DataStreamBytes(const CDataStream& ss) | ||
| 67 | { | ||
| 68 | return vector<unsigned char>(ss.begin(), ss.end()); | ||
| 69 | } | ||
| 70 | |||
| 65 | static bool MakeTempDir(std::string& out) | 71 | static bool MakeTempDir(std::string& out) |
| 66 | { | 72 | { |
| 67 | #ifdef _WIN32 | 73 | #ifdef _WIN32 |
| @@ -1030,6 +1036,104 @@ static int RunDbEnvReopenSelfTest() | |||
| 1030 | return nFail == 0 ? 0 : 1; | 1036 | return nFail == 0 ? 0 : 1; |
| 1031 | } | 1037 | } |
| 1032 | 1038 | ||
| 1039 | static int RunWalletSQLiteSelfTest() | ||
| 1040 | { | ||
| 1041 | fflush(stdout); | ||
| 1042 | printf("wallet-sqlite self-test\n"); | ||
| 1043 | |||
| 1044 | std::string tmp; | ||
| 1045 | if (!MakeTempDir(tmp)) | ||
| 1046 | { | ||
| 1047 | printf(" FAIL could not create a temporary data directory\n"); | ||
| 1048 | return 1; | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | int nFail = 0; | ||
| 1052 | string strPath = tmp + "/wallet.sqlite"; | ||
| 1053 | string strError; | ||
| 1054 | |||
| 1055 | try | ||
| 1056 | { | ||
| 1057 | CDataStream ssKeySetting(SER_DISK); | ||
| 1058 | ssKeySetting << make_pair(string("setting"), string("sqlite-roundtrip")); | ||
| 1059 | CDataStream ssValueSetting(SER_DISK); | ||
| 1060 | ssValueSetting << (int64)424242; | ||
| 1061 | |||
| 1062 | vector<unsigned char> vchPubKey; | ||
| 1063 | vchPubKey.push_back(0x04); | ||
| 1064 | for (int i = 0; i < 64; i++) | ||
| 1065 | vchPubKey.push_back((unsigned char)i); | ||
| 1066 | CDataStream ssKeyDefault(SER_DISK); | ||
| 1067 | ssKeyDefault << string("defaultkey"); | ||
| 1068 | CDataStream ssValueDefault(SER_DISK); | ||
| 1069 | ssValueDefault << vchPubKey; | ||
| 1070 | |||
| 1071 | { | ||
| 1072 | CWalletDBSQLite db; | ||
| 1073 | nFail += Check(db.Open(strPath, strError), | ||
| 1074 | "SQLite wallet opens and creates schema") ? 0 : 1; | ||
| 1075 | nFail += Check(db.BeginTransaction(strError), | ||
| 1076 | "SQLite wallet begins a write transaction") ? 0 : 1; | ||
| 1077 | nFail += Check(db.WriteRecord(DataStreamBytes(ssKeySetting), | ||
| 1078 | DataStreamBytes(ssValueSetting), | ||
| 1079 | strError), | ||
| 1080 | "SQLite wallet writes a serialized setting record") ? 0 : 1; | ||
| 1081 | nFail += Check(db.WriteRecord(DataStreamBytes(ssKeyDefault), | ||
| 1082 | DataStreamBytes(ssValueDefault), | ||
| 1083 | strError), | ||
| 1084 | "SQLite wallet writes a serialized defaultkey record") ? 0 : 1; | ||
| 1085 | nFail += Check(db.CommitTransaction(strError), | ||
| 1086 | "SQLite wallet commits a write transaction") ? 0 : 1; | ||
| 1087 | int nCount = 0; | ||
| 1088 | nFail += Check(db.CountRecords(nCount, strError) && nCount == 2, | ||
| 1089 | "SQLite wallet counts raw records") ? 0 : 1; | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | { | ||
| 1093 | CWalletDBSQLite db; | ||
| 1094 | nFail += Check(db.Open(strPath, strError), | ||
| 1095 | "SQLite wallet reopens") ? 0 : 1; | ||
| 1096 | vector<unsigned char> vchRead; | ||
| 1097 | nFail += Check(db.ReadRecord(DataStreamBytes(ssKeySetting), | ||
| 1098 | vchRead, | ||
| 1099 | strError), | ||
| 1100 | "SQLite wallet reads a serialized setting record") ? 0 : 1; | ||
| 1101 | int64 nReadValue = 0; | ||
| 1102 | CDataStream ssRead(vchRead, SER_DISK); | ||
| 1103 | ssRead >> nReadValue; | ||
| 1104 | nFail += Check(!ssRead.fail() && nReadValue == 424242, | ||
| 1105 | "SQLite wallet preserves serialized values byte-for-byte") ? 0 : 1; | ||
| 1106 | |||
| 1107 | vchRead.clear(); | ||
| 1108 | nFail += Check(db.ReadRecord(DataStreamBytes(ssKeyDefault), | ||
| 1109 | vchRead, | ||
| 1110 | strError), | ||
| 1111 | "SQLite wallet reads a serialized defaultkey record") ? 0 : 1; | ||
| 1112 | vector<unsigned char> vchReadPubKey; | ||
| 1113 | CDataStream ssReadDefault(vchRead, SER_DISK); | ||
| 1114 | ssReadDefault >> vchReadPubKey; | ||
| 1115 | nFail += Check(!ssReadDefault.fail() && vchReadPubKey == vchPubKey, | ||
| 1116 | "SQLite wallet preserves public key blobs") ? 0 : 1; | ||
| 1117 | } | ||
| 1118 | } | ||
| 1119 | catch (const std::exception& e) | ||
| 1120 | { | ||
| 1121 | printf(" FAIL exception: %s\n", e.what()); | ||
| 1122 | nFail++; | ||
| 1123 | } | ||
| 1124 | catch (...) | ||
| 1125 | { | ||
| 1126 | printf(" FAIL unknown exception\n"); | ||
| 1127 | nFail++; | ||
| 1128 | } | ||
| 1129 | |||
| 1130 | RemoveTree(tmp); | ||
| 1131 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 1132 | nFail, nFail == 1 ? "" : "s"); | ||
| 1133 | fflush(stdout); | ||
| 1134 | return nFail == 0 ? 0 : 1; | ||
| 1135 | } | ||
| 1136 | |||
| 1033 | static void ClearWalletRuntimeForTest() | 1137 | static void ClearWalletRuntimeForTest() |
| 1034 | { | 1138 | { |
| 1035 | CRITICAL_BLOCK(cs_mapKeys) | 1139 | CRITICAL_BLOCK(cs_mapKeys) |
| @@ -2289,6 +2393,8 @@ int RunSelfTest(const std::string& name) | |||
| 2289 | return RunWalletStorageSanitySelfTest(); | 2393 | return RunWalletStorageSanitySelfTest(); |
| 2290 | if (name == "db-env-reopen") | 2394 | if (name == "db-env-reopen") |
| 2291 | return RunDbEnvReopenSelfTest(); | 2395 | return RunDbEnvReopenSelfTest(); |
| 2396 | if (name == "wallet-sqlite") | ||
| 2397 | return RunWalletSQLiteSelfTest(); | ||
| 2292 | if (name == "wallet-crypto") | 2398 | if (name == "wallet-crypto") |
| 2293 | return RunWalletCryptoSelfTest(); | 2399 | return RunWalletCryptoSelfTest(); |
| 2294 | if (name == "wallet-encrypt") | 2400 | if (name == "wallet-encrypt") |
| @@ -2309,6 +2415,6 @@ int RunSelfTest(const std::string& name) | |||
| 2309 | return RunManagedTorSelfTest(); | 2415 | return RunManagedTorSelfTest(); |
| 2310 | 2416 | ||
| 2311 | printf("Unknown self-test '%s'\n", name.c_str()); | 2417 | printf("Unknown self-test '%s'\n", name.c_str()); |
| 2312 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); | 2418 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); |
| 2313 | return 1; | 2419 | return 1; |
| 2314 | } | 2420 | } |
diff --git a/src/wallet_sqlite.cpp b/src/wallet_sqlite.cpp new file mode 100644 index 0000000..4104ac6 --- /dev/null +++ b/src/wallet_sqlite.cpp | |||
| @@ -0,0 +1,254 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | |||
| 4 | #include "headers.h" | ||
| 5 | #include "wallet_sqlite.h" | ||
| 6 | |||
| 7 | static void SetSQLiteError(sqlite3* pdb, const string& strContext, | ||
| 8 | string& strError) | ||
| 9 | { | ||
| 10 | if (pdb) | ||
| 11 | strError = strContext + ": " + sqlite3_errmsg(pdb); | ||
| 12 | else | ||
| 13 | strError = strContext; | ||
| 14 | } | ||
| 15 | |||
| 16 | CWalletDBSQLite::CWalletDBSQLite() : pdb(NULL) | ||
| 17 | { | ||
| 18 | } | ||
| 19 | |||
| 20 | CWalletDBSQLite::~CWalletDBSQLite() | ||
| 21 | { | ||
| 22 | Close(); | ||
| 23 | } | ||
| 24 | |||
| 25 | bool CWalletDBSQLite::Open(const string& strPath, string& strError) | ||
| 26 | { | ||
| 27 | Close(); | ||
| 28 | |||
| 29 | int ret = sqlite3_open_v2(strPath.c_str(), &pdb, | ||
| 30 | SQLITE_OPEN_READWRITE | | ||
| 31 | SQLITE_OPEN_CREATE | | ||
| 32 | SQLITE_OPEN_FULLMUTEX, | ||
| 33 | NULL); | ||
| 34 | if (ret != SQLITE_OK) | ||
| 35 | { | ||
| 36 | SetSQLiteError(pdb, "cannot open SQLite wallet", strError); | ||
| 37 | Close(); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | sqlite3_busy_timeout(pdb, 5000); | ||
| 42 | |||
| 43 | if (!Exec("PRAGMA journal_mode=WAL;", strError)) | ||
| 44 | { | ||
| 45 | Close(); | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | if (!Exec("PRAGMA synchronous=FULL;", strError)) | ||
| 49 | { | ||
| 50 | Close(); | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | if (!InitSchema(strError)) | ||
| 54 | { | ||
| 55 | Close(); | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | return true; | ||
| 59 | } | ||
| 60 | |||
| 61 | void CWalletDBSQLite::Close() | ||
| 62 | { | ||
| 63 | if (pdb) | ||
| 64 | { | ||
| 65 | sqlite3_close(pdb); | ||
| 66 | pdb = NULL; | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | bool CWalletDBSQLite::BeginTransaction(string& strError) | ||
| 71 | { | ||
| 72 | if (!pdb) | ||
| 73 | { | ||
| 74 | strError = "SQLite wallet is not open"; | ||
| 75 | return false; | ||
| 76 | } | ||
| 77 | return Exec("BEGIN IMMEDIATE;", strError); | ||
| 78 | } | ||
| 79 | |||
| 80 | bool CWalletDBSQLite::CommitTransaction(string& strError) | ||
| 81 | { | ||
| 82 | if (!pdb) | ||
| 83 | { | ||
| 84 | strError = "SQLite wallet is not open"; | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | return Exec("COMMIT;", strError); | ||
| 88 | } | ||
| 89 | |||
| 90 | bool CWalletDBSQLite::RollbackTransaction(string& strError) | ||
| 91 | { | ||
| 92 | if (!pdb) | ||
| 93 | { | ||
| 94 | strError = "SQLite wallet is not open"; | ||
| 95 | return false; | ||
| 96 | } | ||
| 97 | return Exec("ROLLBACK;", strError); | ||
| 98 | } | ||
| 99 | |||
| 100 | bool CWalletDBSQLite::Exec(const char* pszSql, string& strError) | ||
| 101 | { | ||
| 102 | char* pszErr = NULL; | ||
| 103 | int ret = sqlite3_exec(pdb, pszSql, NULL, NULL, &pszErr); | ||
| 104 | if (ret != SQLITE_OK) | ||
| 105 | { | ||
| 106 | if (pszErr) | ||
| 107 | { | ||
| 108 | strError = pszErr; | ||
| 109 | sqlite3_free(pszErr); | ||
| 110 | } | ||
| 111 | else | ||
| 112 | SetSQLiteError(pdb, "SQLite exec failed", strError); | ||
| 113 | return false; | ||
| 114 | } | ||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | bool CWalletDBSQLite::InitSchema(string& strError) | ||
| 119 | { | ||
| 120 | return Exec("CREATE TABLE IF NOT EXISTS wallet_records (" | ||
| 121 | "key BLOB PRIMARY KEY NOT NULL," | ||
| 122 | "value BLOB NOT NULL" | ||
| 123 | ");", | ||
| 124 | strError) && | ||
| 125 | Exec("PRAGMA user_version=1;", strError); | ||
| 126 | } | ||
| 127 | |||
| 128 | bool CWalletDBSQLite::WriteRecord(const vector<unsigned char>& vchKey, | ||
| 129 | const vector<unsigned char>& vchValue, | ||
| 130 | string& strError) | ||
| 131 | { | ||
| 132 | if (!pdb) | ||
| 133 | { | ||
| 134 | strError = "SQLite wallet is not open"; | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | if (vchKey.empty()) | ||
| 138 | { | ||
| 139 | strError = "SQLite wallet record key is empty"; | ||
| 140 | return false; | ||
| 141 | } | ||
| 142 | |||
| 143 | sqlite3_stmt* stmt = NULL; | ||
| 144 | const char* pszSql = | ||
| 145 | "INSERT OR REPLACE INTO wallet_records(key, value) VALUES(?, ?);"; | ||
| 146 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 147 | if (ret != SQLITE_OK) | ||
| 148 | { | ||
| 149 | SetSQLiteError(pdb, "cannot prepare SQLite wallet write", strError); | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), | ||
| 154 | SQLITE_TRANSIENT); | ||
| 155 | if (ret == SQLITE_OK) | ||
| 156 | ret = sqlite3_bind_blob(stmt, 2, | ||
| 157 | vchValue.empty() ? NULL : &vchValue[0], | ||
| 158 | (int)vchValue.size(), | ||
| 159 | SQLITE_TRANSIENT); | ||
| 160 | if (ret == SQLITE_OK) | ||
| 161 | ret = sqlite3_step(stmt); | ||
| 162 | |||
| 163 | bool fOk = (ret == SQLITE_DONE); | ||
| 164 | if (!fOk) | ||
| 165 | SetSQLiteError(pdb, "cannot write SQLite wallet record", strError); | ||
| 166 | sqlite3_finalize(stmt); | ||
| 167 | return fOk; | ||
| 168 | } | ||
| 169 | |||
| 170 | bool CWalletDBSQLite::ReadRecord(const vector<unsigned char>& vchKey, | ||
| 171 | vector<unsigned char>& vchValueRet, | ||
| 172 | string& strError) | ||
| 173 | { | ||
| 174 | vchValueRet.clear(); | ||
| 175 | if (!pdb) | ||
| 176 | { | ||
| 177 | strError = "SQLite wallet is not open"; | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | if (vchKey.empty()) | ||
| 181 | { | ||
| 182 | strError = "SQLite wallet record key is empty"; | ||
| 183 | return false; | ||
| 184 | } | ||
| 185 | |||
| 186 | sqlite3_stmt* stmt = NULL; | ||
| 187 | const char* pszSql = "SELECT value FROM wallet_records WHERE key = ?;"; | ||
| 188 | int ret = sqlite3_prepare_v2(pdb, pszSql, -1, &stmt, NULL); | ||
| 189 | if (ret != SQLITE_OK) | ||
| 190 | { | ||
| 191 | SetSQLiteError(pdb, "cannot prepare SQLite wallet read", strError); | ||
| 192 | return false; | ||
| 193 | } | ||
| 194 | |||
| 195 | ret = sqlite3_bind_blob(stmt, 1, &vchKey[0], (int)vchKey.size(), | ||
| 196 | SQLITE_TRANSIENT); | ||
| 197 | if (ret != SQLITE_OK) | ||
| 198 | { | ||
| 199 | SetSQLiteError(pdb, "cannot bind SQLite wallet key", strError); | ||
| 200 | sqlite3_finalize(stmt); | ||
| 201 | return false; | ||
| 202 | } | ||
| 203 | |||
| 204 | ret = sqlite3_step(stmt); | ||
| 205 | if (ret == SQLITE_ROW) | ||
| 206 | { | ||
| 207 | const unsigned char* pch = | ||
| 208 | (const unsigned char*)sqlite3_column_blob(stmt, 0); | ||
| 209 | int nBytes = sqlite3_column_bytes(stmt, 0); | ||
| 210 | if (nBytes > 0 && pch) | ||
| 211 | vchValueRet.assign(pch, pch + nBytes); | ||
| 212 | sqlite3_finalize(stmt); | ||
| 213 | return true; | ||
| 214 | } | ||
| 215 | |||
| 216 | if (ret == SQLITE_DONE) | ||
| 217 | strError = "SQLite wallet record not found"; | ||
| 218 | else | ||
| 219 | SetSQLiteError(pdb, "cannot read SQLite wallet record", strError); | ||
| 220 | sqlite3_finalize(stmt); | ||
| 221 | return false; | ||
| 222 | } | ||
| 223 | |||
| 224 | bool CWalletDBSQLite::CountRecords(int& nRecordsRet, string& strError) | ||
| 225 | { | ||
| 226 | nRecordsRet = 0; | ||
| 227 | if (!pdb) | ||
| 228 | { | ||
| 229 | strError = "SQLite wallet is not open"; | ||
| 230 | return false; | ||
| 231 | } | ||
| 232 | |||
| 233 | sqlite3_stmt* stmt = NULL; | ||
| 234 | int ret = sqlite3_prepare_v2(pdb, | ||
| 235 | "SELECT COUNT(*) FROM wallet_records;", | ||
| 236 | -1, &stmt, NULL); | ||
| 237 | if (ret != SQLITE_OK) | ||
| 238 | { | ||
| 239 | SetSQLiteError(pdb, "cannot prepare SQLite wallet count", strError); | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | |||
| 243 | ret = sqlite3_step(stmt); | ||
| 244 | if (ret != SQLITE_ROW) | ||
| 245 | { | ||
| 246 | SetSQLiteError(pdb, "cannot count SQLite wallet records", strError); | ||
| 247 | sqlite3_finalize(stmt); | ||
| 248 | return false; | ||
| 249 | } | ||
| 250 | |||
| 251 | nRecordsRet = sqlite3_column_int(stmt, 0); | ||
| 252 | sqlite3_finalize(stmt); | ||
| 253 | return true; | ||
| 254 | } | ||
diff --git a/src/wallet_sqlite.h b/src/wallet_sqlite.h new file mode 100644 index 0000000..9812d3a --- /dev/null +++ b/src/wallet_sqlite.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // Copyright (c) 2026 Bitflash developers | ||
| 2 | // Distributed under the MIT/X11 software license. | ||
| 3 | |||
| 4 | #ifndef BITFLASH_WALLET_SQLITE_H | ||
| 5 | #define BITFLASH_WALLET_SQLITE_H | ||
| 6 | |||
| 7 | #include <sqlite3.h> | ||
| 8 | |||
| 9 | class CWalletDBSQLite | ||
| 10 | { | ||
| 11 | public: | ||
| 12 | CWalletDBSQLite(); | ||
| 13 | ~CWalletDBSQLite(); | ||
| 14 | |||
| 15 | bool Open(const string& strPath, string& strError); | ||
| 16 | void Close(); | ||
| 17 | |||
| 18 | bool BeginTransaction(string& strError); | ||
| 19 | bool CommitTransaction(string& strError); | ||
| 20 | bool RollbackTransaction(string& strError); | ||
| 21 | |||
| 22 | bool WriteRecord(const vector<unsigned char>& vchKey, | ||
| 23 | const vector<unsigned char>& vchValue, | ||
| 24 | string& strError); | ||
| 25 | bool ReadRecord(const vector<unsigned char>& vchKey, | ||
| 26 | vector<unsigned char>& vchValueRet, | ||
| 27 | string& strError); | ||
| 28 | bool CountRecords(int& nRecordsRet, string& strError); | ||
| 29 | |||
| 30 | private: | ||
| 31 | sqlite3* pdb; | ||
| 32 | |||
| 33 | bool Exec(const char* pszSql, string& strError); | ||
| 34 | bool InitSchema(string& strError); | ||
| 35 | |||
| 36 | CWalletDBSQLite(const CWalletDBSQLite&); | ||
| 37 | void operator=(const CWalletDBSQLite&); | ||
| 38 | }; | ||
| 39 | |||
| 40 | #endif | ||